Asp Net Core PDF
Asp Net Core PDF
Para alterar a amostra que executará o cenário ExpandDefault , defina o símbolo ExpandDefault e deixe os
símbolos restantes comentados de fora:
Para obter mais informações sobre como usar diretivas de pré-processador C# para compilar seletivamente as
seções de código, consulte #define (Referência C#) e #if (Referência C#) .
Regiões no código de exemplo
Alguns aplicativos de exemplo contém seções de código cercadas pelas instruções C# #region e #endregion. O
sistema de build de documentação injeta essas regiões nos tópicos renderizados da documentação.
Os nomes das regiões geralmente contêm a palavra "snippet". O exemplo a seguir mostra uma região chamada
snippet_FilterInCode :
#region snippet_FilterInCode
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logging =>
logging.AddFilter("System", LogLevel.Debug)
.AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Trace))
.Build();
#endregion
O snippet de código C# precedente é referenciado no arquivo de markdown do tópico com a seguinte linha:
[!code-csharp[](sample/SampleApp/Program.cs?name=snippet_FilterInCode)]
Você pode ignorar (ou remover) com segurança as instruções #region e #endregion que envolvem o código.
Não altere o código dentro dessas instruções se você planeja executar os cenários de exemplo descritos no
tópico. Fique à vontade para alterar o código ao experimentar com outros cenários.
Para obter mais informações, veja Contribuir para a documentação do ASP.NET: snippets de código.
Próximas etapas
Para obter mais informações, consulte os seguintes recursos:
Introdução a Páginas do Razor
Publicar um aplicativo ASP.NET Core no Azure com o Visual Studio
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.
Escolher entre o ASP.NET 4.x e o ASP.NET Core
08/01/2019 • 3 minutes to read • Edit Online
O ASP.NET Core é uma reformulação do ASP.NET 4. x. Este artigo lista as diferenças entre eles.
ASP.NET Core
O ASP.NET Core é uma estrutura de software livre, multiplataforma, para a criação de aplicativos Web modernos e
baseados em nuvem, no Windows, no macOS ou no Linux.
O ASP.NET Core oferece os seguintes benefícios:
Uma história unificada para a criação da interface do usuário da Web e das APIs Web.
Projetado para capacidade de teste.
O Razor Pages torna a codificação de cenários focados em página mais fácil e produtiva.
Capacidade de desenvolver e executar no Windows, macOS e Linux.
De software livre e voltado para a comunidade.
Integração de estruturas modernas do lado do cliente e fluxos de trabalho de desenvolvimento.
Um sistema de configuração pronto para a nuvem, baseado no ambiente.
Injeção de dependência interna.
Um pipeline de solicitação HTTP leve, modular e de alto desempenho.
A capacidade de hospedar no IIS, Nginx, Apache, Docker ou hospedar em seu próprio processo.
Controle de versão do aplicativo do lado a lado ao direcionar .NET Core.
Ferramentas que simplificam o moderno desenvolvimento para a Web.
ASP.NET 4.x
O ASP.NET 4.x é uma estrutura consolidada que fornece os serviços necessários para criar aplicativos Web
baseados em servidor, de nível empresarial, no Windows.
Seleção de estrutura
A tabela a seguir compara o ASP.NET Core com o ASP.NET 4. x.
Páginas Razor é a abordagem recomendada para criar uma Use o Web Forms, o SignalR, o MVC, a API Web, Webhooks
interface do usuário da Web começando com o ASP.NET Core ou páginas da Web
2.x. Confira também MVC, API Web e SignalR.
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
Confira ASP.NET Core targeting .NET Framework (ASP.NET Core direcionado para o .NET Framework) para obter
informações sobre o suporte do ASP.NET Core 2.x no .NET Framework.
Recursos adicionais
Introdução ao ASP.NET
Introdução ao ASP.NET Core
Implantar aplicativos ASP.NET Core no Serviço de Aplicativo do Azure
Tutorial: Introdução ao ASP.NET Core
17/01/2019 • 3 minutes to read • Edit Online
Este tutorial mostra como usar a interface de linha de comando do .NET Core para criar um aplicativo Web
ASP.NET Core.
Você aprenderá como:
Criar um projeto de aplicativo Web.
Habilitar o HTTPS local.
Execute o aplicativo.
Editar uma página do Razor.
No final, você terá um aplicativo Web de trabalho em execução no seu computador local.
Pré-requisitos
SDK do .NET Core 2.2
Executar o aplicativo
Execute os seguintes comandos:
cd aspnetcoreapp
dotnet run
Depois que o shell de comando indicar que o aplicativo foi iniciado, navegue até https://localhost:5001. Clique em
Aceitar para aceitar a política de privacidade e cookies. Este aplicativo não armazena informações pessoais.
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Hello, world! The time on the server is @DateTime.Now</p>
</div>
Próximas etapas
Neste tutorial, você aprendeu como:
Criar um projeto de aplicativo Web.
Habilitar o HTTPS local.
Execute o projeto.
Faça uma alteração.
Para saber mais sobre o ASP.NET Core, confira a introdução:
Introdução ao ASP.NET Core
Novidades do ASP.NET Core 2.2
30/01/2019 • 11 minutes to read • Edit Online
Este artigo destaca as alterações mais significativas no ASP.NET Core 2.2, com links para a documentação
relevante.
Verificações de integridade
Um novo serviço de verificações de integridade facilita o uso do ASP.NET Core em ambientes que exigem
verificações de integridade, como o Kubernetes. As verificações de integridade incluem middleware e um conjunto
de bibliotecas que definem um serviço e uma abstração IHealthCheck .
As verificações de integridade são usadas por um orquestrador de contêineres ou um balanceador de carga para
determinar rapidamente se um sistema está respondendo às solicitações normalmente. Um orquestrador de
contêineres pode responder a uma verificação de integridade com falha interrompendo uma implantação sem
interrupção ou reiniciando um contêiner. Um balanceador de carga pode responder a uma verificação de
integridade encaminhando o tráfego para fora da instância com falha do serviço.
As verificações de integridade são expostas por um aplicativo como um ponto de extremidade HTTP usado por
sistemas de monitoramento. As verificações de integridade podem ser configuradas para uma variedade de
cenários de monitoramento em tempo real e sistemas de monitoramento. O serviço de verificações de integridade
é integrado ao projeto BeatPulse. que facilita a adição de verificações para dezenas de sistemas e dependências
populares.
Para obter mais informações, confira Verificações de integridade no ASP.NET Core.
HTTP/2 no Kestrel
O ASP.NET Core 2.2 adiciona suporte ao HTTP/2.
O HTTP/2 é uma revisão principal do protocolo HTTP. Alguns dos recursos importantes do HTTP/2 são o suporte
à compactação de cabeçalho e fluxos totalmente multiplexados em uma única conexão. Embora o HTTP/2 preserve
a semântica do HTTP (cabeçalhos HTTP, métodos etc.), ele é uma alteração da falha do HTTP/1.x com relação a
como esses dados são estruturados e enviados pela conexão.
Como consequência dessa alteração no enquadramento, os servidores e os clientes precisam negociar a versão de
protocolo usada. O recurso ALPN (Negociação de Protocolo da Camada de Aplicativo) é uma extensão TLS com a
qual o servidor e o cliente podem negociar a versão de protocolo usada como parte do handshake TLS. Embora
seja possível ter um conhecimento prévio entre o servidor e o cliente sobre o protocolo, todos os principais
navegadores dão suporte ALPN como a única maneira de estabelecer uma conexão HTTP/2.
Para obter mais informações, confira Suporte ao HTTP/2.
Configuração do Kestrel
Em versões anteriores do ASP.NET Core, as opções do Kestrel são configuradas por meio da chamada a
UseKestrel . No 2.2, as opções do Kestrel são configuradas por meio da chamada a ConfigureKestrel no
construtor do host. Essa alteração resolve um problema com a ordem dos registros IServer para a hospedagem
em processo. Para obter mais informações, consulte os seguintes recursos:
Atenuar conflitos do UseIIS
Configurar opções do servidor Kestrel com ConfigureKestrel
Melhorias do CORS
Em versões anteriores do ASP.NET Core, o Middleware do CORS permite o envio dos cabeçalhos Accept ,
Accept-Language , Content-Language e Origin , independentemente dos valores configurados em
CorsPolicy.Headers . No 2.2, uma correspondência de política do Middleware do CORS só é possível quando os
cabeçalhos enviados em Access-Control-Request-Headers corresponder exatamente aos cabeçalhos indicados em
WithHeaders .
Compactação de resposta
O ASP.NET Core 2.2 pode compactar respostas com o formato de compactação Brotli.
Para obter mais informações, confira O middleware de compactação de resposta dá suporte à compactação Brotli.
Modelos de projeto
Os modelos de projeto Web ASP.NET Core foram atualizados para o Bootstrap 4 e o Angular 6. A nova aparência
é visualmente mais simples e facilita a visualização das estruturas importantes do aplicativo.
Desempenho de validação
O sistema de validação do MVC foi projetado para ser extensível e flexível, permitindo que você determine a cada
solicitação quais validadores se aplicam a determinado modelo. Isso é ótimo para a criação de provedores de
validação complexa. No entanto, na maioria dos casos, um aplicativo usa apenas os validadores internos e não
exige essa flexibilidade extra. Validadores internos incluem DataAnnotations como [Required] e [StringLength], e
IValidatableObject .
No ASP.NET Core 2.2, o MVC poderá causar um curto-circuito na validação se ele determinar que um grafo de
modelo fornecido não exige validação. Ignorar a validação resulta em melhorias significativas ao validar modelos
que não podem ou não têm nenhum validador. Isso inclui objetos, como coleções de primitivos (como byte[] ,
string[] , Dictionary<string, string> ), ou grafos de objeto complexo sem muitos validadores.
Informações adicionais
Para obter a lista completa de alterações, confira as Notas sobre a versão do ASP.NET Core 2.2.
Novidades do ASP.NET Core 2.1
30/10/2018 • 12 minutes to read • Edit Online
Este artigo destaca as alterações mais significativas no ASP.NET Core 2.1, com links para a documentação
relevante.
SignalR
O SignalR foi reescrito para ASP.NET Core 2.1. O SignalR do ASP.NET Core inclui uma série de melhorias:
Um modelo de expansão simplificado.
Um novo cliente JavaScript sem dependência jQuery.
Um novo protocolo binário compacto com base em MessagePack.
Suporte para protocolos personalizados.
Um novo modelo de resposta de transmissão.
Suporte para clientes com base em WebSockets básicos.
Para obter mais informações, veja ASP.NET Core SignalR.
HTTPS
Com o foco cada vez maior em segurança e privacidade, é importante habilitar HTTPS para aplicativos Web. A
imposição de HTTPS está se tornando cada vez mais rígida na Web. Sites que não usam HTTPS são considerados
inseguros. Navegadores (Chrome, Mozilla) estão começando a impor o uso de recursos Web em um contexto de
seguro. O RGPD requer o uso de HTTPS para proteger a privacidade do usuário. Quando usar HTTPS em
produção for fundamental, usar HTTPS no desenvolvimento pode ajudar a evitar problemas de implantação (por
exemplo, links inseguros). O ASP.NET Core 2.1 inclui uma série de melhorias que tornam mais fácil usar HTTPS
em desenvolvimento e configurar HTTPS em produção. Para obter mais informações, veja Impor HTTPS.
Ativo por padrão
Para facilitar o desenvolvimento de site seguro, HTTPS está habilitado por padrão. Da versão 2.1 em diante, o
Kestrel escuta em https://localhost:5001 quando um certificado de desenvolvimento local está presente. Um
certificado de desenvolvimento é criado:
Como parte da experiência de primeira execução do SDK do .NET Core, quando você usa o SDK pela primeira
vez.
Manualmente usando a nova ferramenta dev-certs .
Execute dotnet dev-certs https --trust para confiar no certificado.
Redirecionamento e imposição de HTTPS
Aplicativos Web geralmente precisam escutar em HTTP e HTTPS, mas, então redirecionam todo o tráfego HTTP
para HTTPS. Na versão 2.1, foi introduzido o middleware de redirecionamento de HTTPS especializado que
redireciona de modo inteligente com base na presença de portas do servidor associado ou de configuração.
O uso de HTTPS pode ser imposto ainda mais empregando HSTS (Protocolo de Segurança do Transporte Estrita
HTTP ). HSTS instrui navegadores a sempre acessarem o site por meio de HTTPS. O ASP.NET Core 2.1 adiciona
middleware HSTS compatível com opções para idade máxima, subdomínios e a lista de pré-carregamento de
HSTS.
Configuração para produção
Em produção, HTTPS precisa ser configurado explicitamente. Na versão 2.1, foi adicionado o esquema de
configuração padrão para configurar HTTPS para Kestrel. Os aplicativos podem ser configurados para usar:
Vários pontos de extremidade incluindo as URLs. Para obter mais informações, veja Implementação do servidor
Web Kestrel: configuração de ponto de extremidade.
O certificado a ser usado para HTTPS de um arquivo no disco ou de um repositório de certificados.
RGPD
O ASP.NET Core fornece APIs e modelos para ajudar a atender alguns dos requisitos do RGPD (Regulamento de
Proteção de Dados Geral) da UE. Para obter mais informações, veja Suporte RGPD no ASP.NET Core. Um
aplicativo de exemplo mostra como usar e permite que você teste a maioria dos pontos de extensão RGPD e APIs
adicionados aos modelos do ASP.NET Core 2.1.
Testes de integração
É introduzido um novo pacote que simplifica a criação e a execução do teste. O pacote
Microsoft.AspNetCore.Mvc.Testing lida com as seguintes tarefas:
Copia o arquivo de dependência (*.deps) do aplicativo testado para a pasta bin do projeto de teste.
Define a raiz de conteúdo para a raiz do projeto do aplicativo testado para que arquivos estáticos e
páginas/exibições sejam encontrados quando os testes forem executados.
Fornece a classe WebApplicationFactory para simplificar a inicialização do aplicativo testado com TestServer.
O teste a seguir usa xUnit para verificar se a página de índice é carregada com um código de status de êxito e o
cabeçalho Content-Type correto:
public class BasicTests
: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly HttpClient _client;
[Fact]
public async Task GetHomePage()
{
// Act
var response = await _client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
[ApiController], ActionResult<T>
O ASP.NET Core 2.1 adiciona novas convenções de programação que facilitam o build de APIs Web limpas e
descritivas. ActionResult<T> é um novo tipo adicionado para permitir que um aplicativo retorne um tipo de
resposta ou qualquer outro resultado da ação (semelhante a IActionResult), enquanto ainda indica o tipo de
resposta. O atributo [ApiController] também foi adicionado como a maneira de aceitar convenções e
comportamentos específicos da API Web.
Para obter mais informações, veja Criar APIs Web com ASP.NET Core.
IHttpClientFactory
O ASP.NET Core 2.1 inclui um novo serviço IHttpClientFactory que torna mais fácil configurar e consumir
instâncias de HttpClient em aplicativos. O HttpClient já tem o conceito de delegar manipuladores que podem
ser vinculados uns aos outros para solicitações HTTP de saída. O alocador:
Torna o registro de instâncias de HttpClient por cliente nomeado mais intuitivo.
Implementa um manipulador Polly que permite que políticas Polly sejam usadas para Retry, CircuitBreakers etc.
Para obter mais informações, veja Iniciar solicitações de HTTP.
Informações adicionais
Para obter uma lista de alterações, vejas as Notas de versão do ASP.NET Core 2.1.
Novidades do ASP.NET Core 2.0
30/10/2018 • 12 minutes 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
Introdução a 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.
IConfiguration no contêiner de serviços torna mais fácil para aplicativos recuperarem 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 o
[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
Tornamos mais fácil criar 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 autenticador no ASP.NET Core
Migrar a autenticação e a 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 saber como obter os outros modelos, confira Criar um novo projeto de SPA. Para
obter informações de como criar um SPA no ASP.NET Core, confira Usar 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 do aplicativo solicitar o conteúdo com um cabeçalho de solicitação de intervalo, o ASP.NET Core
reconhecerá a solicitação e lidará com o cabeçalho. Se parte do conteúdo solicitado puder ser entregue, o
ASP.NET Core ignorará a parte em questão 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 mais informações, confira Compilação e pré-compilação de exibição Razor no ASP.NET Core.
<LangVersion>latest</LangVersion>
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:
Migrar do ASP.NET Core 1.x para o ASP.NET Core 2.0
Migrar a autenticação e a 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
10/01/2019 • 2 minutes 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.
Tutorial: Criar uma API Web com o ASP.NET Core
MVC
30/01/2019 • 28 minutes 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
Se você receber uma caixa de diálogo perguntando se você deve confiar no certificado do IIS Express, selecione
Sim. Na caixa de diálogo Aviso de Segurança exibida em seguida, selecione Sim.
O seguinte JSON é retornado:
["value1","value2"]
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 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)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP
//request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for
// production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
O código anterior:
Remove as declarações using não utilizadas.
Adiciona o contexto de banco de dados ao contêiner de DI.
Especifica que o contexto de banco de dados usará um banco de dados em memória.
Adicionar um controlador
Visual Studio
Visual Studio Code/Visual Studio para Mac
Clique com o botão direito do mouse na pasta Controllers.
Selecione Adicionar > Novo Item.
Na caixa de diálogo Adicionar Novo Item, selecione o modelo Classe do Controlador de API.
Dê à classe o nome TodoController e selecione Adicionar.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TodoApi.Models;
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TodoController : ControllerBase
{
private readonly TodoContext _context;
if (_context.TodoItems.Count() == 0)
{
// Create a new TodoItem if collection is empty,
// which means you can't delete all TodoItems.
_context.TodoItems.Add(new TodoItem { Name = "Item1" });
_context.SaveChanges();
}
}
}
}
O código anterior:
Define uma classe de controlador de API sem métodos.
Decore a classe com o atributo [ApiController] . Esse atributo indica se o controlador responde às solicitações
da API Web. Para obter informações sobre comportamentos específicos habilitados pelo atributo, confira
Anotação com o atributo ApiController.
Usa a DI 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.
Adiciona um item chamado Item1 ao banco de dados se o banco de dados está vazio. Esse código está no
construtor, de modo que ele seja executado sempre que há uma nova solicitação HTTP. Se você excluir todos os
itens, o construtor criará Item1 novamente na próxima vez que um método de API for chamado. Portanto, pode
parecer que a exclusão não funcionou quando ela realmente funcionou.
// GET: api/Todo
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Substitua [controller] pelo nome do controlador, que é o nome de classe do controlador menos o sufixo
"Controlador" por convenção. Para esta amostra, o nome da classe do controlador é TodoController e,
portanto, o nome do controlador é "todo". O roteamento do ASP.NET Core não diferencia maiúsculas de
minúsculas.
Se o atributo [HttpGet] tiver um modelo de rota (por exemplo, [HttpGet("products")] ), acrescente isso ao
caminho. Esta amostra não usa um modelo. Para obter mais informações, confira Roteamento de atributo
com atributos Http[Verb].
No método GetTodoItem a seguir, "{id}" é uma variável de espaço reservado para o identificador exclusivo do
item pendente. Quando GetTodoItem é invocado, o valor de "{id}" na URL é fornecido para o método no
parâmetro id .
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
Valores de retorno
O tipo de retorno dos métodos GetTodoItems e GetTodoItem é o tipo ActionResult<T>. O ASP.NET Core serializa
automaticamente o objeto em JSON e grava o JSON no corpo da mensagem de resposta. O código de resposta
para esse tipo de retorno é 200, supondo que não haja nenhuma exceção sem tratamento. As exceções sem
tratamento são convertidas em erros 5xx.
Os tipos de retorno ActionResult podem representar uma ampla variedade de códigos de status HTTP. Por
exemplo, GetTodoItem pode retornar dois valores de status diferentes:
Se nenhum item corresponder à ID solicitada, o método retornará um código de erro 404 NotFound.
Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar item resulta em uma
resposta HTTP 200.
WARNING
Habilite novamente a verificação do certificado SSL depois de testar o controlador.
O código anterior é um método HTTP POST, conforme indicado pelo atributo [HttpPost]. O método obtém o valor
do item pendente no corpo da solicitação HTTP.
O método CreatedAtAction :
retorna um código de status HTTP 201 em caso de êxito. HTTP 201 é a resposta padrão para um método
HTTP POST que cria um novo recurso no servidor.
Adiciona um cabeçalho Location à resposta. O cabeçalho Location especifica o URI do item de tarefas
pendentes recém-criado. Para obter mais informações, confira 10.2.2 201 Criado.
Faz referência à ação GetTodoItem para criar o URI de Location do cabeçalho. A palavra-chave nameof do
C# é usada para evitar o hard-coding do nome da ação, na chamada CreatedAtAction .
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
{
"name":"walk dog",
"isComplete":true
}
Selecione Enviar.
Se você receber um erro 405 Método Não Permitido, provavelmente, esse será o resultado da não
compilação do projeto após a adição do método PostTodoItem .
Testar o URI do cabeçalho de local
Selecione a guia Cabeçalhos no painel Resposta.
Copie o valor do cabeçalho Local:
Defina o método como GET.
Cole o URI (por exemplo, https://localhost:5001/api/Todo/2 )
Selecione Enviar.
// PUT: api/Todo/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
{
if (id != item.Id)
{
return BadRequest();
}
_context.Entry(item).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}
PutTodoItem é semelhante a PostTodoItem , exceto pelo uso de HTTP PUT. A resposta é 204 (Sem conteúdo). De
acordo com a especificação de HTTP, uma solicitação PUT exige que o cliente envie a entidade inteira atualizada,
não apenas as alterações. Para dar suporte a atualizações parciais, use HTTP PATCH.
Testar o método PutTodoItem
Atualize o item pendente que tem a ID = 1 e defina seu nome como "feed fish":
{
"ID":1,
"name":"feed fish",
"isComplete":true
}
// DELETE: api/Todo/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Save">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Adicione um arquivo JavaScript chamado site.js ao diretório wwwroot. Substitua seu conteúdo pelo código a
seguir:
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
tr.appendTo(tBody);
});
todos = data;
}
});
}
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function(key, item) {
if (item.id === id) {
$("#edit-name").val(item.name);
$("#edit-id").val(item.id);
$("#edit-isComplete")[0].checked = item.isComplete;
}
});
$("#spoiler").css({ display: "block" });
}
$(".my-form").on("submit", function() {
const item = {
name: $("#edit-name").val(),
isComplete: $("#edit-isComplete").is(":checked"),
id: $("#edit-id").val()
};
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$("#spoiler").css({ display: "none" });
}
Uma alteração nas configurações de inicialização do projeto ASP.NET Core pode ser necessária para testar a
página HTML localmente:
Abra Properties\launchSettings.json.
Remova a propriedade launchUrl para forçar o aplicativo a ser aberto em index.html, o arquivo padrão do
projeto.
Há várias maneiras de obter o jQuery. No snippet anterior, a biblioteca é carregada de uma CDN.
Esta amostra chama todos os métodos CRUD da API. Veja a seguir explicações das chamadas à API.
Obter uma lista de itens pendentes
A função ajax do jQuery envia uma solicitação GET para a API, que retorna o JSON que representa uma matriz de
itens pendentes. A função de retorno de chamada success será invocada se a solicitação for bem-sucedida. No
retorno de chamada, o DOM é atualizado com as informações do item pendente.
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
tr.appendTo(tBody);
});
todos = data;
}
});
}
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
Recursos adicionais
Exibir ou baixar o código de exemplo para este tutorial. Consulte como baixar.
Para obter mais informações, consulte os seguintes recursos:
Criar APIs Web com o ASP.NET Core
Páginas de ajuda da API Web ASP.NET Core com o Swagger/OpenAPI
Páginas Razor do ASP.NET Core com EF Core – série de tutoriais
Roteamento para ações do controlador no ASP.NET Core
Tipos de retorno de ação do controlador na API Web ASP.NET Core
Implantar aplicativos ASP.NET Core no Serviço de Aplicativo do Azure
Hospedar e implantar o ASP.NET Core
Próximas etapas
Neste tutorial, você aprendeu como:
Criar um projeto de API Web.
Adicionar uma classe de modelo.
Criar o contexto de banco de dados.
Registrar o contexto de banco de dados.
Adicionar um controlador.
Adicionar métodos CRUD.
Configurar o roteamento e caminhos de URL.
Especificar os valores retornados.
Chamar a API Web com o Postman.
Chamar a API Web com o jQuery.
Avance para o próximo tutorial para saber como gerar páginas de ajuda da API:
Introdução ao Swashbuckle e ao ASP.NET Core
Criar uma API Web com o ASP.NET Core e o
MongoDB
06/02/2019 • 13 minutes to read • Edit Online
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
SDK 2.2 ou posterior do .NET Core
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
MongoDB
Configurar o MongoDB
Se usar o Windows, o MongoDB será instalado em C:\Arquivos de Programas\MongoDB por padrão. Adicione
C:\Arquivos de Programas\MongoDB\Servidor\<número_de_versão>\bin à variável de ambiente Path . Essa
alteração possibilita o acesso ao MongoDB a partir de qualquer lugar em seu computador de desenvolvimento.
Use o Shell do mongo nas etapas a seguir para criar um banco de dados, fazer coleções e armazenar documentos.
Para saber mais sobre os comandos de Shell do mongo, consulte Como trabalhar com o Shell do mongo.
1. Escolha um diretório no seu computador de desenvolvimento para armazenar os dados. Por exemplo,
C:\BooksData no Windows. Crie o diretório se não houver um. O Shell do mongo não cria novos diretórios.
2. Abra um shell de comando. Execute o comando a seguir para se conectar ao MongoDB na porta padrão
27017. Lembre-se de substituir <data_directory_path> pelo diretório escolhido na etapa anterior.
3. Abra outra instância do shell de comando. Conecte-se ao banco de dados de testes padrão executando o
seguinte comando:
mongo
use BookstoreDb
Se ele ainda não existir, um banco de dados chamado BookstoreDb será criado. Se o banco de dados existir, a
conexão dele será aberta para transações.
5. Crie uma coleção Books usando o seguinte comando:
db.createCollection('Books')
{ "ok" : 1 }
6. Defina um esquema para a coleção Books e insira dois documentos usando o seguinte comando:
db.Books.insertMany([{'Name':'Design Patterns','Price':54.93,'Category':'Computers','Author':'Ralph
Johnson'}, {'Name':'Clean Code','Price':43.15,'Category':'Computers','Author':'Robert C. Martin'}])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5bfd996f7b8e48dc15ff215d"),
ObjectId("5bfd996f7b8e48dc15ff215e")
]
}
db.Books.find({}).pretty()
{
"_id" : ObjectId("5bfd996f7b8e48dc15ff215d"),
"Name" : "Design Patterns",
"Price" : 54.93,
"Category" : "Computers",
"Author" : "Ralph Johnson"
}
{
"_id" : ObjectId("5bfd996f7b8e48dc15ff215e"),
"Name" : "Clean Code",
"Price" : 43.15,
"Category" : "Computers",
"Author" : "Robert C. Martin"
}
O esquema adiciona uma propriedade _id gerada automaticamente do tipo ObjectId para cada
documento.
O banco de dados está pronto. Você pode começar a criar a API Web do ASP.NET Core.
Adicionar um modelo
1. Adicione um diretório Modelos à raiz do projeto.
2. Adicione uma classe Book ao diretório Modelos com o seguinte código:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace BooksApi.Models
{
public class Book
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("Name")]
public string BookName { get; set; }
[BsonElement("Price")]
public decimal Price { get; set; }
[BsonElement("Category")]
public string Category { get; set; }
[BsonElement("Author")]
public string Author { get; set; }
}
}
Outras propriedades na classe são anotadas com o atributo [BsonElement] . O valor do atributo representa o nome
da propriedade da coleção do MongoDB.
using System.Collections.Generic;
using System.Linq;
using BooksApi.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
namespace BooksApi.Services
{
public class BookService
{
private readonly IMongoCollection<Book> _books;
{
"ConnectionStrings": {
"BookstoreDb": "mongodb://localhost:27017"
},
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
O registro de serviço anterior é necessário para dar suporte à injeção do construtor no consumo de classes.
A classe BookService usa os seguintes membros MongoDB.Driver para executar operações CRUD em relação ao
banco de dados:
MongoClient – Lê a instância do servidor para executar operações de banco de dados. O construtor dessa
classe é fornecido na cadeia de conexão do MongoDB:
IMongoDatabase – Representa o banco de dados Mongo para execução de operações. Este tutorial usa o
método genérico GetCollection<T>(collection) na interface para obter acesso a dados em uma coleção
específica. Operações CRUD podem ser executadas em relação à coleção depois que esse método é
chamado. Na chamada de método GetCollection<T>(collection) :
collection representa o nome da coleção.
T representa o tipo de objeto CLR armazenado na coleção.
Adicionar um controlador
1. Adicione uma classe BooksController ao diretório Controladores com o seguinte código:
using System.Collections.Generic;
using BooksApi.Models;
using BooksApi.Services;
using Microsoft.AspNetCore.Mvc;
namespace BooksApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly BookService _bookService;
[HttpGet]
public ActionResult<List<Book>> Get()
{
return _bookService.Get();
}
if (book == null)
{
return NotFound();
}
return book;
}
[HttpPost]
public ActionResult<Book> Create(Book book)
{
_bookService.Create(book);
[HttpPut("{id:length(24)}")]
public IActionResult Update(string id, Book bookIn)
{
var book = _bookService.Get(id);
if (book == null)
{
return NotFound();
}
_bookService.Update(id, bookIn);
return NoContent();
return NoContent();
}
[HttpDelete("{id:length(24)}")]
public IActionResult Delete(string id)
{
var book = _bookService.Get(id);
if (book == null)
{
return NotFound();
}
_bookService.Remove(book.Id);
return NoContent();
}
}
}
[
{
"id":"5bfd996f7b8e48dc15ff215d",
"bookName":"Design Patterns",
"price":54.93,
"category":"Computers",
"author":"Ralph Johnson"
},
{
"id":"5bfd996f7b8e48dc15ff215e",
"bookName":"Clean Code",
"price":43.15,
"category":"Computers",
"author":"Robert C. Martin"
}
]
Próximas etapas
Para saber mais sobre a criação de APIs Web do ASP.NET Core, confira os seguintes recursos:
Criar APIs Web com o ASP.NET Core
Tipos de retorno de ação do controlador na API Web ASP.NET Core
Criar serviços de back-end para aplicativos móveis
nativos com o ASP.NET Core
01/10/2018 • 14 minutes 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.
NOTE
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 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>();
}
TIP
Saiba mais sobre como criar APIs Web em Criar sua primeira API Web com o ASP.NET Core MVC e o 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();
}
Recursos adicionais
Autenticação e autorização
Tutorial: Criar um aplicativo Web de Páginas do
Razor com o ASP.NET Core
04/02/2019 • 2 minutes to read • Edit Online
Esta série de tutoriais explica as noções básicas sobre a criação de um aplicativo Web Razor Pages.
Para obter uma introdução mais avançada direcionada a desenvolvedores experientes, confira Introdução a Razor
Pages.
Esta série inclui os seguintes tutoriais:
1. Criar um aplicativo Web de Páginas do Razor
2. Adicionar um modelo a um aplicativo de Páginas do Razor
3. Gerar páginas do Razor por scaffolding
4. Trabalhar com um banco de dados
5. Atualizar páginas do Razor
6. Adicionar pesquisa
7. Adicionar um novo campo
8. Adicionar validação
No final, você terá um aplicativo que pode exibir e gerenciar um banco de dados de filmes.
Criar um aplicativo Web com o ASP.NET Core MVC
23/01/2019 • 2 minutes to read • Edit Online
Este tutorial ensina a usar o desenvolvimento Web do ASP.NET Core MVC com controladores e exibições. Se você
é novo no desenvolvimento da Web ASP.NET Core, considere a versão Razor Pages deste tutorial, que oferece um
ponto inicial mais simples.
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
Tutorial: Introdução ao SignalR para ASP.NET Core
28/01/2019 • 11 minutes to read • Edit Online
Este tutorial ensina as noções básicas da criação de um aplicativo em tempo real usando o SignalR. Você aprenderá
como:
Crie um projeto Web.
Adicionar uma biblioteca de clientes do SignalR.
Criar um hub do SignalR.
Configurar o projeto para usar o SignalR.
Adicione o código que envia mensagens de qualquer cliente para todos os clientes conectados.
No final, você terá um aplicativo de chat funcionando:
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
SDK 2.2 ou posterior do .NET Core
Selecione Aplicativo Web para criar um projeto que usa Razor Pages.
Selecione uma estrutura de destino do .NET Core, selecione ASP.NET Core 2.2 e clique em OK.
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
A classe ChatHub é herda da classe Hub do SignalR. A classe Hub gerencia conexões, grupos e sistemas de
mensagens.
O método SendMessage pode ser chamado por um cliente conectado para enviar uma mensagem a todos os
clientes. O código cliente do JavaScript que chama o método é mostrado posteriormente no tutorial. O
código do SignalR é assíncrono para fornecer o máximo de escalabilidade.
Configurar o SignalR
O servidor do SignalR precisa ser configurado para passar solicitações do SignalR ao SignalR.
Adicione o seguinte código realçado ao arquivo Startup.cs.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SignalRChat.Hubs;
namespace SignalRChat
{
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)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a
given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
}
// 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();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
app.UseMvc();
}
}
}
@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/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>
O código anterior:
Cria as caixas de texto para o nome e a mensagem de texto e um botão Enviar.
Cria uma lista com id="messagesList" para exibir as mensagens recebidas do hub do SignalR.
Inclui referências de script ao SignalR e ao código do aplicativo chat.js que você criará na próxima etapa.
Na pasta wwwroot/js, crie um arquivo chat.js com o código a seguir:
"use strict";
connection.start().then(function(){
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
O código anterior:
Cria e inicia uma conexão.
Adiciona no botão Enviar um manipulador que envia mensagens ao hub.
Adiciona no objeto de conexão um manipulador que recebe mensagens do hub e as adiciona à lista.
Executar o aplicativo
Visual Studio
Visual Studio Code
Visual Studio para Mac
Pressione CTRL + F5 para executar o aplicativo sem depuração.
Copie a URL da barra de endereços, abra outra instância ou guia do navegador e cole a URL na barra de
endereços.
Escolha qualquer navegador, insira um nome e uma mensagem e selecione o botão Enviar Mensagem.
O nome e a mensagem são exibidos em ambas as páginas instantaneamente.
TIP
Se o aplicativo não funcionar, abra as ferramentas para desenvolvedores do navegador (F12) e acesse o console. Você pode
encontrar erros relacionados ao código HTML e JavaScript. Por exemplo, suponha que você coloque signalr.js em uma pasta
diferente daquela direcionada. Nesse caso, a referência a esse arquivo não funcionará e ocorrerá um erro 404 no console.
Próximas etapas
Neste tutorial, você aprendeu como:
Criar um projeto de aplicativo Web.
Adicionar uma biblioteca de clientes do SignalR.
Criar um hub do SignalR.
Configurar o projeto para usar o SignalR.
Adicionar o código que usa o hub para enviar mensagens de qualquer cliente para todos os clientes conectados.
Para saber mais sobre o SignalR, confira a introdução:
Introdução ao ASP.NET Core SignalR
Usar o SignalR do ASP.NET Core com TypeScript e
Webpack
28/01/2019 • 19 minutes to read • Edit Online
Prerequisites
Visual Studio
Visual Studio Code
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
SDK 2.2 ou posterior do .NET Core
npm init -y
{
"name": "SignalRWebPack",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Definir a propriedade private como true evita avisos de instalação de pacote na próxima etapa.
3. Instalar os pacotes de npm exigidos. Execute o seguinte comando na raiz do projeto:
npm install -D -E [email protected] [email protected] [email protected] mini-css-
[email protected] [email protected] [email protected] [email protected] [email protected]
"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},
module.exports = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "wwwroot"),
filename: "[name].[chunkhash].js",
publicPath: "/"
},
resolve: {
extensions: [".js", ".ts"]
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
new CleanWebpackPlugin(["wwwroot/*"]),
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
new MiniCssExtractPlugin({
filename: "css/[name].[chunkhash].css"
})
]
};
*, *::before, *::after {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
}
.input-zone {
align-items: center;
display: flex;
flex-direction: row;
margin: 10px;
}
.input-zone-input {
flex: 1;
margin-right: 10px;
}
.message-author {
font-weight: bold;
}
.messages {
border: 1px solid #000;
margin: 10px;
max-height: 300px;
min-height: 300px;
overflow-y: auto;
padding: 5px;
}
O código precedente configura o compilador TypeScript para produzir um JavaScript compatível com
ECMAScript 5.
11. Crie src/index.ts com o seguinte conteúdo:
import "./css/main.css";
btnSend.addEventListener("click", send);
function send() {
}
O TypeScript precedente recupera referências a elementos DOM e anexa dois manipuladores de eventos:
keyup : esse evento é acionado quando o usuário digita algo na caixa de texto identificada como
tbMessage . A função send é chamada quando o usuário pressionar a tecla Enter.
click : esse evento é acionado quando o usuário clica no botão Enviar. A função send é chamada.
app.UseDefaultFiles();
app.UseStaticFiles();
O código precedente permite que o servidor localize e forneça o arquivo index.html, se o usuário inserir a
URL completa ou a URL raiz do aplicativo Web.
2. Chame AddSignalR no método Startup.ConfigureServices . Adiciona serviços SignalR ao seu projeto.
services.AddSignalR();
3. Mapeie uma rota /hub para o hub ChatHub . Adicione as linhas a seguir ao final do método
Startup.Configure :
app.UseSignalR(options =>
{
options.MapHub<ChatHub>("/hub");
});
4. Crie um novo diretório, chamado Hubs, na raiz do projeto. A finalidade é armazenar o hub SignalR, que é
criado na próxima etapa.
5. Crie o hub Hubs/ChatHub.cs com o código a seguir:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
}
}
6. Adicione o código a seguir ao topo do arquivo Startup.cs para resolver a referência ChatHub :
using SignalRWebPack.Hubs;
O comando precedente instala o cliente TypeScript do SignalR, que permite ao cliente enviar mensagens
para o servidor.
2. Adicione o código destacado ao arquivo src/index.ts:
import "./css/main.css";
import * as signalR from "@aspnet/signalr";
m.innerHTML =
`<div class="message-author">${username}</div><div>${message}</div>`;
divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});
btnSend.addEventListener("click", send);
function send() {
}
messageContainer.innerHTML =
`<div class="message-author">${username}</div><div>${message}</div>`;
divMessages.appendChild(messageContainer);
divMessages.scrollTop = divMessages.scrollHeight;
});
btnSend.addEventListener("click", send);
function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => tbMessage.value = "");
}
Enviar uma mensagem por meio da conexão WebSockets exige uma chamada para o método send . O
primeiro parâmetro do método é o nome da mensagem. Os dados da mensagem residem nos outros
parâmetros. Neste exemplo, uma mensagem identificada como newMessage é enviada ao servidor. A
mensagem é composta do nome de usuário e da entrada em uma caixa de texto. Se o envio for bem-
sucedido, o valor da caixa de texto será limpo.
4. Adicione o método em destaque à classe ChatHub :
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
public async Task NewMessage(string username, string message)
{
await Clients.All.SendAsync("messageReceived", username, message);
}
}
}
O código precedente transmite as mensagens recebidas para todos os usuários conectados quando o
servidor as recebe. Não é necessário ter um método genérico on para receber todas as mensagens. Um
método nomeado com o nome da mensagem é suficiente.
Neste exemplo, o cliente TypeScript envia uma mensagem identificada como newMessage . O método
NewMessage de C# espera os dados enviados pelo cliente. Uma chamada é feita para o método SendAsync
em Clients.All. As mensagens recebidas são enviadas a todos os clientes conectados ao hub.
Testar o aplicativo
Confirme que o aplicativo funciona com as seguintes etapas.
Visual Studio
Visual Studio Code
1. Execute o Webpack no modo de versão. Usando a janela Console do Gerenciador de Pacotes, execute o
comando a seguir na raiz do projeto. Se você não estiver na raiz do projeto, insira cd SignalRWebPack antes
de inserir o comando.
Este comando suspende o fornecimento dos ativos do lado do cliente ao executar o aplicativo. Os ativos são
colocados na pasta wwwroot.
O Webpack concluiu as seguintes tarefas:
Limpou os conteúdos do diretório wwwroot.
Converteu o TypeScript para JavaScript, um processo conhecido como transpilação.
Reduziu o tamanho do arquivo JavaScript gerado, um processo conhecido como minificação.
Copiou os arquivos JavaScript, CSS e HTML processados do src para o diretório wwwroot.
Injetou os seguintes elementos no arquivo wwwroot/index.html:
Uma marca <link> , que referencia o arquivo wwwroot/main.<hash>.css. Essa marca é colocada
imediatamente antes do fim da marca </head> .
Uma marca <script> , que referencia o arquivo minificado wwwroot/main.<hash>.js. Essa marca
é colocada imediatamente antes do fim da marca </body> .
2. Selecione Debug > Iniciar sem depuração para iniciar o aplicativo em um navegador sem anexar o
depurador. O arquivo wwwroot/index.html é fornecido em http://localhost:<port_number> .
3. Abra outra instância do navegador (qualquer navegador). Cole a URL na barra de endereços.
4. Escolha qualquer navegador, digite algo na caixa de texto Mensagem e clique no botão Enviar. O nome de
usuário exclusivo e a mensagem são exibidas em ambas as páginas instantaneamente.
Recursos adicionais
ASP.NET Core SignalR JavaScript cliente
Usando os hubs de SignalR do ASP.NET Core
Páginas Razor do ASP.NET Core com EF Core – série
de tutoriais
11/07/2018 • 2 minutes to read • Edit Online
Esta série de tutoriais ensina a criar aplicativos Web de Razor Pages do ASP.NET Core que usam o EF (Entity
Framework) Core para o acesso a dados.
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
ASP.NET Core MVC com EF Core – série de tutoriais
21/06/2018 • 2 minutes 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.
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
Conceitos básicos do ASP.NET Core
09/01/2019 • 14 minutes to read • Edit Online
Um aplicativo ASP.NET Core é um aplicativo de console que cria um servidor Web em seu método Program.Main .
O método Main é o ponto de entrada gerenciado do aplicativo:
host.Run();
}
}
Inicialização
O método UseStartup em WebHostBuilder especifica a classe Startup para seu aplicativo:
host.Run();
}
}
A classe Startup é onde os serviços exigidos pelo aplicativo são configurados e o pipeline de tratamento de
solicitações é definido. A classe Startup deve ser pública e geralmente contém os seguintes métodos.
Startup.ConfigureServices é opcional.
ConfigureServices define os Serviços usados pelo seu aplicativo (por exemplo, ASP.NET Core MVC, Entity
Framework Core, Identity etc.). Configure define o middleware chamado no pipeline de solicitação.
Para obter mais informações, consulte Inicialização de aplicativo no ASP.NET Core.
Raiz do conteúdo
A raiz do conteúdo é o caminho base para qualquer conteúdo usado pelo aplicativo, tal como Razor Pages,
exibições do MVC e ativos estáticos. Por padrão, a raiz do conteúdo é a mesma localização que o caminho base do
aplicativo para o executável que hospeda o aplicativo.
Middleware
No ASP.NET Core, você compõe o pipeline de solicitação usando o middleware. O middleware do ASP.NET Core
executa operações assíncronas em um HttpContext e então invoca o próximo middleware no pipeline ou encerra a
solicitação.
Por convenção, um componente de middleware chamado "XYZ" é adicionado ao pipeline invocando-se um
método de extensão UseXYZ no método Configure .
O ASP.NET Core inclui um conjunto de middleware interno, e você pode escrever seu próprio middleware
personalizado. A OWIN (Open Web Interface para .NET), que permite que aplicativos Web sejam desacoplados de
servidores Web, é compatível com aplicativos ASP.NET Core.
Para obter mais informações, consulte Middleware do ASP.NET Core e OWIN (Open Web Interface para .NET)
com o ASP.NET Core.
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 uma variável de ambiente, um arquivo de configurações e um argumento de linha de
comando.
Para obter mais informações, consulte Usar vários ambientes no ASP.NET Core.
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 Host da Web e Host Genérico no ASP.NET Core.
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.
Windows
macOS
Linux
O ASP.NET Core vem com as seguintes implementações de servidor:
O servidor Kestrel é um servidor Web gerenciado multiplataforma. O Kestrel normalmente é executado em
uma configuração de proxy reverso que usa o IIS. O Kestrel também pode ser executado como um servidor de
borda voltado para o público exposto diretamente à Internet no ASP.NET Core 2.0 ou posterior.
O servidor HTTP do IIS ( IISHttpServer ) é um servidor em processo do IIS.
O servidor HTTP.sys é um servidor Web do ASP.NET Core no Windows.
Windows
macOS
Linux
O ASP.NET Core vem com as seguintes implementações de servidor:
O servidor Kestrel é um servidor Web gerenciado multiplataforma. O Kestrel normalmente é executado em
uma configuração de proxy reverso que usa o IIS. O Kestrel também pode ser executado como um servidor de
borda voltado para o público exposto diretamente à Internet no ASP.NET Core 2.0 ou posterior.
O servidor HTTP.sys é um servidor Web do ASP.NET Core no Windows.
Para obter mais informações, consulte Implementações de servidor Web em ASP.NET Core.
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), variáveis de ambiente e argumentos de linha de comando. Você
também pode escrever seus próprios provedores de configuração personalizados.
Para obter mais informações, consulte Configuração no ASP.NET Core.
Registro 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.
Para obter mais informações, consulte Registro em log no ASP.NET Core.
Tratamento de erros
O ASP.NET Core tem cenários internos para tratamento de 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, consulte Tratar erros no ASP.NET Core.
Roteamento
O ASP.NET Core oferece cenários para roteamento de solicitações de aplicativo para manipuladores de rotas.
Para obter mais informações, consulte Roteamento no ASP.NET Core.
Acessar o HttpContext
HttpContext está disponível automaticamente durante o processamento de solicitações com Razor Pages e com o
MVC. Em circunstâncias em que HttpContext não está prontamente disponível, você pode acessar o HttpContext
por meio da interface IHttpContextAccessor e sua implementação padrão, HttpContextAccessor.
Para obter mais informações, consulte Acessar o HttpContext no ASP.NET Core.
Inicialização de aplicativo no ASP.NET Core
21/01/2019 • 12 minutes to read • Edit Online
A classe Startup
Os aplicativos do ASP.NET Core usam uma classe Startup , que é chamada de Startup por convenção. A
classe Startup :
Opcionalmente, inclua um método ConfigureServices para configurar os serviços do aplicativo. Um serviço
é um componente reutilizável que fornece a funcionalidade do aplicativo. Os serviços estão configurados—
também descritos como registrados—em ConfigureServices e consumidos no aplicativo por meio da DI
(injeção de dependência) ou ApplicationServices.
Inclui 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:
A classe Startup é especificada para o aplicativo quando o host do aplicativo é criado. O host do aplicativo é
compilado quando Build é chamado no construtor do host na classe Program . A classe Startup geralmente é
especificada chamando o método WebHostBuilderExtensions.UseStartup<TStartup> no construtor do host:
O host fornece serviços que estão disponíveis para o construtor de classe Startup . O aplicativo adiciona
serviços adicionais por meio de ConfigureServices . Os serviços de aplicativos e o host ficam, então, disponíveis
em Configure e em todo o aplicativo.
Um uso comum da injeção de dependência na classe Startup é injetar:
IHostingEnvironment para configurar serviços pelo ambiente.
IConfigurationBuilder para ler a configuração.
ILoggerFactory para criar um agente em Startup.ConfigureServices .
if (_env.IsDevelopment())
{
// Development service configuration
logger.LogInformation("Development environment");
}
else
{
// Non-development service configuration
logger.LogInformation($"Environment: {_env.EnvironmentName}");
}
Uma alternativa a injetar IHostingEnvironment é usar uma abordagem baseada em convenções. Quando o
aplicativo define classes Startup separadas para ambientes diferentes (por exemplo, StartupDevelopment ), a
classe Startup apropriada é selecionada no 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, veja Usar vários ambientes.
Para saber mais sobre o host, confira Host da Web e Host Genérico no ASP.NET Core. 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 antes do método Configure para configurar os serviços do aplicativo.
Onde as opções de configuração são definidas por convenção.
O padrão típico consiste em chamar todos os métodos Add{Service} e, em seguida, chamar todos os métodos
services.Configure{Service} . Por exemplo, veja o tópico Configurar serviços de identidade.
O host pode configurar alguns serviços antes que métodos Startup sejam chamados. Para obter mais
informações, consulte Host da Web e Host Genérico no ASP.NET Core.
Para recursos que exigem uma configuração significativa, há métodos de extensão Add{Service} em
IServiceCollection. Um aplicativo ASP.NET Core típico registra serviços para o Entity Framework, Identity e
MVC:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
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 por meio da injeção de dependência ou de ApplicationServices.
O método Configure
O método Configure é usado para especificar como o aplicativo responde às 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 .
Os modelos do ASP.NET Core configuram o pipeline com suporte para:
Página de exceção do desenvolvedor
Manipulador de exceção
Segurança de Transporte Estrita de HTTP (HSTS )
Redirecionamento de HTTPS
Arquivos estáticos
RGPD (Regulamento Geral sobre a Proteção de Dados) da UE
MVC do ASP.NET Core e Razor Pages
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
Cada método de extensão Use adiciona um ou mais componentes 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 o MVC como o manipulador padrão.
Cada componente de middleware no pipeline de solicitação é responsável por invocar o próximo componente
no pipeline ou causar um curto-circuito da cadeia, se apropriado. Se o curto-circuito não ocorrer ao longo da
cadeia de middleware, cada middleware terá uma segunda chance de processar a solicitação antes que ela seja
enviada ao cliente.
Serviços adicionais, como IHostingEnvironment e ILoggerFactory , também podem ser especificados na
assinatura do método Configure . Quando especificados, serviços adicionais são injetados quando estão
disponíveis.
Para obter mais informações de como usar o IApplicationBuilder e a ordem de processamento de
middleware, confira Middleware do ASP.NET Core.
Métodos de conveniência
Para configurar serviços e o pipeline de processamento de solicitação sem usar uma classe Startup , chame os
métodos de conveniência ConfigureServices e Configure no construtor do host. Diversas chamadas para
ConfigureServices são acrescentadas umas às outras. Se houver várias chamadas de método Configure , a
última chamada de Configure será usada.
public class Program
{
public static IHostingEnvironment HostingEnvironment { get; set; }
public static IConfiguration Configuration { get; set; }
logger.LogInformation("Logged in Configure");
if (HostingEnvironment.IsDevelopment())
{
...
}
else
{
...
}
...
});
}
public RequestSetOptionsMiddleware(
RequestDelegate next, IOptions<AppOptions> injectedOptions)
{
_next = next;
_injectedOptions = injectedOptions;
}
if (!string.IsNullOrWhiteSpace(option))
{
_injectedOptions.Value.Option = WebUtility.HtmlEncode(option);
}
await _next(httpContext);
}
}
WebHost.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddTransient<IStartupFilter,
RequestSetOptionsStartupFilter>();
})
.UseStartup<Startup>()
.Build();
Quando um parâmetro de cadeia de caracteres de consulta para option é fornecido, o middleware processa a
atribuição de valor antes que o middleware do MVC renderize a resposta:
A ordem de execução do middleware é definida pela ordem dos registros IStartupFilter :
Várias implementações de IStartupFilter podem interagir com os mesmos objetos. Se a ordem for
importante, ordene seus registros de serviço IStartupFilter para corresponder à ordem em que os
middlewares devem ser executados.
Bibliotecas podem adicionar middleware com uma ou mais implementações de IStartupFilter que são
executadas antes ou depois de outro middleware de aplicativo registrado com IStartupFilter . Para invocar
um middleware IStartupFilter antes de um middleware adicionado pelo IStartupFilter de uma
biblioteca, posicione o registro do serviço antes da biblioteca ser adicionada ao contêiner de serviço. Para
invocá-lo posteriormente, posicione o registro do serviço após a biblioteca ser adicionada.
Recursos adicionais
Host da Web e Host Genérico no ASP.NET Core
Usar vários ambientes no ASP.NET Core
Middleware do ASP.NET Core
Registro em log no ASP.NET Core
Configuração no ASP.NET Core
Injeção de dependência no ASP.NET Core
10/11/2018 • 31 minutes to read • Edit Online
return Task.FromResult(0);
}
}
Uma instância da classe MyDependency pode ser criada para tornar o método WriteMessage
disponível para uma classe. A classe MyDependency é uma dependência da classe IndexModel
:
Uma instância da classe MyDependency pode ser criada para tornar o método WriteMessage
disponível para uma classe. A classe MyDependency é uma dependência da classe
HomeController :
public class HomeController : Controller
{
MyDependency _dependency = new MyDependency();
return View();
}
}
return Task.FromResult(0);
}
}
return Task.FromResult(0);
}
}
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
NOTE
Cada método de extensão services.Add{SERVICE_NAME} adiciona (e possivelmente configura)
serviços. Por exemplo, services.AddMvc() adiciona os serviços exigidos pelo MVC e por Razor
Pages. Recomendamos que os aplicativos sigam essa convenção. Coloque os métodos de extensão
no namespace Microsoft.Extensions.DependencyInjection para encapsular grupos de registros de
serviço.
// Use myStringValue
}
...
}
Uma instância do serviço é solicitada por meio do construtor de uma classe, onde o serviço
é usado e atribuído a um campo particular. O campo é usado para acessar o serviço
conforme o necessário na classe.
No exemplo de aplicativo, a instância IMyDependency é solicitada e usada para chamar o
método WriteMessage do serviço:
public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
}
// GET: /mydependency/
public async Task<IActionResult> Index()
{
await _myDependency.WriteMessage(
"MyDependencyController.Index created this message.");
return View();
}
}
Microsoft.AspNetCore.Hosting.Builder.IApplicatio Transitório
nBuilderFactory
Microsoft.AspNetCore.Hosting.IApplicationLifetim Singleton
e
Microsoft.AspNetCore.Hosting.IHostingEnvironm Singleton
ent
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transitório
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transitório
Microsoft.Extensions.Logging.ILogger<T> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvid Singleton
er
Microsoft.Extensions.Options.IConfigureOptions< Transitório
T>
Microsoft.Extensions.Options.IOptions<T> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
WARNING
Ao usar um serviço com escopo em um middleware, injete o serviço no método Invoke ou
InvokeAsync . Não injete por meio de injeção de construtor porque isso força o serviço a se
comportar como um singleton. Para obter mais informações, consulte Middleware do ASP.NET
Core.
Singleton
Serviços de tempo de vida singleton são criados na primeira solicitação (ou quando
ConfigureServices é executado e uma instância é especificada com o registro do serviço).
Cada solicitação subsequente usa a mesma instância. Se o aplicativo exigir um
comportamento de singleton, recomendamos permitir que o contêiner do serviço gerencie o
tempo de vida do serviço. Não implemente o padrão de design singleton e forneça o código
de usuário para gerenciar o tempo de vida do objeto na classe.
WARNING
É perigoso resolver um serviço com escopo de um singleton. Pode fazer com que o serviço tenha
um estado incorreto durante o processamento das solicitações seguintes.
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
}
public OperationsController(
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_operationService = operationService;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
_singletonInstanceOperation = singletonInstanceOperation;
}
return View();
}
}
try
{
var serviceContext = services.GetRequiredService<MyScopedService>();
// Use the context here
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
}
host.Run();
}
Validação de escopo
Quando o aplicativo está em execução no ambiente de desenvolvimento, o provedor de
serviço padrão executa verificações para saber se:
Os serviços com escopo não são resolvidos direta ou indiretamente pelo provedor de
serviço raiz.
Os serviços com escopo não são injetados direta ou indiretamente em singletons.
O provedor de serviços raiz é criado quando BuildServiceProvider é chamado. O tempo de
vida do provedor de serviço raiz corresponde ao tempo de vida do aplicativo/servidor
quando o provedor começa com o aplicativo e é descartado quando o aplicativo é desligado.
Os serviços com escopo são descartados pelo contêiner que os criou. Se um serviço com
escopo é criado no contêiner raiz, o tempo de vida do serviço é promovido efetivamente
para singleton, porque ele só é descartado pelo contêiner raiz quando o aplicativo/servidor é
desligado. A validação dos escopos de serviço detecta essas situações quando
BuildServiceProvider é chamado.
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 HttpContext.RequestServices.
Os Serviços de Solicitação representam os serviços configurados e solicitados como parte
do aplicativo. Quando os objetos especificam dependências, elas são atendidas pelos tipos
encontrados em RequestServices , não em ApplicationServices .
Em geral, o aplicativo não deve usar essas propriedades diretamente. Em vez disso, solicite
os tipos exigidos pelas classes por meio de construtores de classe e permita que a estrutura
injete as dependências. Isso resulta em classes que são mais fáceis de testar.
NOTE
Prefira solicitar dependências como parâmetros de construtor para acessar a coleção
RequestServices .
NOTE
No ASP.NET Core 1.0, as chamadas do contêiner descartam todos os objetos IDisposable ,
incluindo aqueles que ele não criou.
// Add Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<DefaultModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
Em tempo de execução, o Autofac é usado para resolver tipos e injetar dependências. Para
saber mais sobre como usar o Autofac com o ASP.NET Core, consulte a documentação do
Autofac.
Acesso thread-safe
Os serviços Singleton precisam ser thread-safe. Se um serviço singleton tiver uma
dependência em um serviço transitório, o serviço transitório também precisará ser thread-
safe, dependendo de como ele é usado pelo singleton.
O método de fábrica de um único serviço, como o segundo argumento para
AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), não precisa
ser thread-safe. Como um construtor do tipo ( static ), ele tem garantia de ser chamado
uma vez por um único thread.
Recomendações
A resolução de serviço baseada em async/await e Task não é compatível. O C# não
é compatível com os construtores assíncronos. Portanto, o padrão recomendado é
usar os métodos assíncronos após a resolução síncrona do serviço.
Evite armazenar dados e a configuração diretamente no contêiner do serviço. Por
exemplo, o carrinho de compras de um usuário normalmente não deve ser
adicionado ao contêiner do serviço. 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 por meio da DI.
Evite o acesso estático aos serviços (por exemplo, digitando estaticamente
IApplicationBuilder.ApplicationServices para usar em outro lugar).
Evite usar o padrão do localizador de serviço. Por exemplo, não invoque GetService
para obter uma instância de serviço quando for possível usar a DI. Outra variação de
localizador de serviço a ser evitada é injetar um alocador que resolve as dependências
em tempo de execução. Essas duas práticas misturam estratégias de inversão de
controle.
Evite o acesso estático a HttpContext (por exemplo,
IHttpContextAccessor.HttpContext).
Como todos os conjuntos de recomendações, talvez você encontre situações em que é
necessário ignorar uma recomendação. As exceções são raras —principalmente casos
especiais dentro da própria estrutura.
A DI é uma alternativa aos padrões de acesso a objeto estático/global. Talvez você não
obtenha os benefícios da DI se combiná-lo com o acesso a objeto estático.
Recursos adicionais
Injeção de dependência em exibições no ASP.NET Core
Injeção de dependência em controladores no ASP.NET Core
Injeção de dependência em manipuladores de requisito no ASP.NET Core
Inicialização de aplicativo no ASP.NET Core
Ativação de middleware baseada em alocador no ASP.NET Core
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
Inversão de Contêineres de Controle e o padrão de Injeção de Dependência (Martin
Fowler)
New is Glue ("colando" o código em uma implementação específica)
Como registrar um serviço com várias interfaces na DI do ASP.NET Core
Roteamento no ASP.NET Core
30/01/2019 • 71 minutes to read • Edit Online
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Para obter mais informações sobre o roteamento baseado em IRouter, confira a versão do ASP.NET Core
2.1 deste tópico.
O roteamento é responsável por mapear URIs de solicitação para manipuladores de rotas e expedir as
solicitações de entrada. As rotas são definidas no aplicativo e configuradas quando o aplicativo é iniciado.
Uma rota pode opcionalmente extrair os valores da URL contida na solicitação e esses valores podem então
ser usados para o processamento da solicitação. Usando rotas configuradas no aplicativo, o roteamento
pode gerar URLs que são mapeadas para manipuladores de rotas.
Para usar os últimos cenários de roteamento no ASP.NET Core 2.1, especifique a versão de compatibilidade
com o registro de serviços MVC em Startup.ConfigureServices :
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
IMPORTANT
Este documento aborda o roteamento de nível inferior do ASP.NET Core. Para obter informações sobre o roteamento
do ASP.NET Core MVC, confira Roteamento para ações do controlador no ASP.NET Core. Para obter informações
sobre convenções de roteamento no Razor Pages, confira Convenções de rota e aplicativo das Páginas do Razor no
ASP.NET Core.
NOTE
Com o lançamento do roteamento de ponto de extremidade no ASP.NET Core 2.2, a vinculação de ponto de
extremidade fica limitada às ações e às páginas do MVC/Razor Pages. As expansões de funcionalidades de vinculação
de ponto de extremidade estão planejadas para versões futuras.
Uma sobrecarga desses métodos aceita argumentos que incluem o HttpContext . Esses métodos são
funcionalmente equivalentes a Url.Action e Url.Page , mas oferecem mais flexibilidade e opções.
Os métodos GetPath* são mais semelhantes a Url.Action e Url.Page , pois geram um URI que contém um
caminho absoluto. Os métodos GetUri* sempre geram um URI absoluto que contém um esquema e um
host. Os métodos que aceitam um HttpContext geram um URI no contexto da solicitação em execução. Os
valores de rota de ambiente, o caminho base da URL, o esquema e o host da solicitação em execução são
usados, a menos que sejam substituídos.
LinkGenerator é chamado com um endereço. A geração de um URI ocorre em duas etapas:
1. Um endereço é associado a uma lista de pontos de extremidade que correspondem ao endereço.
2. O RoutePattern de cada ponto de extremidade é avaliado até que seja encontrado um padrão de rota
correspondente aos valores fornecidos. A saída resultante é combinada com as outras partes de URI
fornecidas ao gerador de link e é retornada.
Os métodos fornecidos pelo LinkGenerator dão suporte a funcionalidades de geração de link padrão para
qualquer tipo de endereço. A maneira mais conveniente usar o gerador de link é por meio de métodos de
extensão que executam operações para um tipo de endereço específico.
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
Suponha que você gere um link para uma ação usando a seguinte rota:
Com o roteamento baseado em IRouter , esse código gera um URI igual a /blog/ReadPost/17 , que
respeita o uso de maiúsculas do valor de rota fornecido. O roteamento de ponto de extremidade no
ASP.NET Core 2.2 ou posterior produz /Blog/ReadPost/17 ("Blog" está em letras maiúsculas). O
roteamento de ponto de extremidade fornece a interface IOutboundParameterTransformer que pode ser
usada para personalizar esse comportamento globalmente ou para aplicar diferentes convenções ao
mapeamento de URLs.
Para obter mais informações, confira a seção Referência de transformador de parâmetro.
A Geração de Link usada pelo MVC/Razor Pages com rotas convencionais tem um comportamento
diferente ao tentar estabelecer um vínculo com um controlador/uma ação ou uma página não
existente.
Considere o seguinte modelo de rota padrão:
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
Suponha que você gere um link para uma ação usando o modelo padrão com o seguinte:
@page "{id}"
@Url.Page("/Login")
Em /Pages/Login.cshtml:
@page "{id?}"
Se o URI é /Store/Product/18 no ASP.NET Core 2.1 ou anterior, o link gerado na página Store/Info
por @Url.Page("/Login") é /Login/18 . O valor de id igual a 18 é reutilizado, mesmo que o destino
do link seja uma parte totalmente diferente do aplicativo. O valor de rota de id no contexto da
página /Login provavelmente é um valor de ID de usuário, e não um valor de ID do produto
(product ID ) da loja.
No roteamento de ponto de extremidade com o ASP.NET Core 2.2 ou posterior, o resultado é /Login
. Os valores de ambiente não são reutilizados quando o destino vinculado é uma ação ou uma página
diferente.
Sintaxe do parâmetro de rota de viagem de ida e volta: as barras "/" não são codificadas ao usar uma
sintaxe do parâmetro catch-all de asterisco duplo ( ** ).
Durante a geração de link, o sistema de roteamento codifica o valor capturado em um parâmetro
catch-all de asterisco duplo ( ** ) (por exemplo, {**myparametername} ), exceto as barras "/". O catch-all
de asterisco duplo é compatível com o roteamento baseado em IRouter no ASP.NET Core 2.2 ou
posterior.
A sintaxe do parâmetro catch-all de asterisco único em versões anteriores do ASP.NET Core (
{*myparametername} ) permanece com suporte e as barras "/" são codificadas.
/search/{**page} /search/admin/products
Exemplo de middleware
No exemplo a seguir, um middleware usa a API de LinkGenerator para criar um link para um método de
ação que lista os produtos da loja. O uso do gerador de link com sua injeção em uma classe e uma chamada
a GenerateLink está disponível para qualquer classe em um aplicativo.
using Microsoft.AspNetCore.Routing;
httpContext.Response.ContentType = "text/plain";
Geração de URL é o processo pelo qual o roteamento pode criar um caminho de URL de acordo com um
conjunto de valores de rota. Isso permite uma separação lógica entre os manipuladores de rotas e as URLs
que os acessam.
A geração de URL segue um processo iterativo semelhante, mas começa com o código da estrutura ou do
usuário chamando o método GetVirtualPath da coleção de rotas. Cada rota tem seu método GetVirtualPath
chamado em sequência, até que um VirtualPathData não nulo seja retornado.
As entradas primárias para GetVirtualPath são:
VirtualPathContext.HttpContext
VirtualPathContext.Values
VirtualPathContext.AmbientValues
As rotas usam principalmente os valores de rota fornecidos por Values e AmbientValues para decidir se é
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. 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 para o caso de
uma rota precisar obter serviços ou dados adicionais associados ao contexto atual.
TIP
Considere VirtualPathContext.Values como um conjunto de substituições de VirtualPathContext.AmbientValues. A
geração de URL tenta reutilizar os valores de rota da solicitação atual para gerar URLs para links usando a mesma rota
ou os mesmos valores de rota.
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
Esse modelo corresponde a um caminho de URL e extrai os valores de rota. Por exemplo, o caminho
/Products/Details/17 gera os seguintes valores de rota:
{ controller = Products, action = Details, id = 17 } .
Os valores de rota são determinados pela divisão do caminho da URL em segmentos e pela correspondência
de cada segmento com o nome do parâmetro de rota no modelo de rota. Os parâmetros de rota são
nomeados. Os parâmetros são definidos com a colocação do nome do parâmetro em chaves { ... } .
O modelo anterior também pode corresponder ao caminho da URL / e produzir os 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.
Os parâmetros de rota com um valor padrão sempre produzem um valor de rota quando a rota corresponde.
Os parâmetros opcionais não produzem um valor de rota quando não há nenhum segmento de caminho de
URL correspondente. Confira a seção Referência de modelo de rota para obter uma descrição completa dos
recursos e da sintaxe de modelo de rota.
No seguinte exemplo, a definição do parâmetro de rota {id:int} define uma restrição de rota para o
parâmetro de rota id :
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id:int}");
Esse modelo corresponde a um caminho de URL, como /Products/Details/17 , mas não como
/Products/Details/Apples . As restrições de rota implementam IRouteConstraint e inspecionam valores de
rota para verificá-los. Neste exemplo, o valor de rota id precisa ser conversível em um inteiro. Confira
route-constraint-reference para obter uma explicação das restrições de rota fornecidas pela estrutura.
Sobrecargas adicionais de MapRoute aceitam valores para constraints , dataTokens e defaults . O uso
típico desses parâmetros é passar um objeto de tipo anônimo, no qual os nomes da propriedade do tipo
anônimo correspondem aos nomes do parâmetro de rota.
Os seguintes exemplos de MapRoute criam rotas equivalentes:
routes.MapRoute(
name: "default_route",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" });
routes.MapRoute(
name: "default_route",
template: "{controller=Home}/{action=Index}/{id?}");
TIP
A sintaxe embutida para a definição de restrições e padrões pode ser interessante para rotas simples. No entanto, há
cenários, como tokens de dados, que não dão suporte à sintaxe embutida.
routes.MapRoute(
name: "blog",
template: "Blog/{**article}",
defaults: new { controller = "Blog", action = "ReadArticle" });
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" });
TIP
Para entender melhor a geração de URL, imagine qual URL você deseja gerar e, em seguida, pense em como um
modelo de rota corresponderia a essa URL. Quais valores serão produzidos? Este é o equivalente aproximado de como
funciona a geração de URL na classe Route .
O seguinte exemplo usa uma rota padrão geral do ASP.NET Core MVC:
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
Com os valores de rota { controller = Products, action = List } , a URL /Products/List é gerada. 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, a URL é gerada com êxito sem um valor para id .
Com os valores de rota { controller = Home, action = Index } , a URL / é gerada. Os valores de rota
fornecidos correspondem aos valores padrão, sendo que os segmentos correspondentes aos valores padrão
são omitidos com segurança.
A viagem de ida e volta gerada pelas duas URLs com a definição de rota a seguir ( /Home/Index e / ) produz
os mesmos valores de rota que foram usados para gerar a URL.
NOTE
Um aplicativo que usa o ASP.NET Core MVC deve usar UrlHelper para gerar URLs, em vez de chamar o roteamento
diretamente.
Para obter mais informações sobre a geração de URL, confira a seção Referência de geração de URL.
As rotas precisam ser configuradas no método Startup.Configure . O aplicativo de exemplo usa as seguintes
APIs:
RouteBuilder
MapGet – Corresponde apenas às solicitações HTTP GET.
UseRouter
var trackPackageRouteHandler = new RouteHandler(context =>
{
var routeValues = context.GetRouteData().Values;
return context.Response.WriteAsync(
$"Hello! Route values: {string.Join(", ", routeValues)}");
});
routeBuilder.MapRoute(
"Track Package Route",
"package/{operation:regex(^track|create|detonate$)}/{id:int}");
URI RESPOSTA
Se você estiver configurando uma única rota, chame UseRouter passando uma instância de IRouter . Você
não precisa usar RouteBuilder.
A estrutura fornece um conjunto de métodos de extensão para a criação de rotas
(RequestDelegateRouteBuilderExtensions):
MapDelete
MapGet
MapMiddlewareDelete
MapMiddlewareGet
MapMiddlewarePost
MapMiddlewarePut
MapMiddlewareRoute
MapMiddlewareVerb
MapPost
MapPut
MapRoute
MapVerb
Alguns dos métodos listados, como MapGet , exigem um RequestDelegate . O RequestDelegate é usado como
o manipulador de rotas quando a rota corresponde. Outros métodos nesta família permitem configurar um
pipeline de middleware a 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, veja MapGet e MapVerb.
Você pode usar um asterisco ( * ) ou um asterisco duplo ( ** ) como um prefixo para um parâmetro de rota
para associá-lo ao restante do URI. Eles são chamados de parâmetros catch-all. Por exemplo, blog/{**slug}
corresponde a qualquer URI que começa com /blog e tem qualquer valor depois dele, que é atribuído ao
valor de rota slug . Os parâmetros catch-all também podem corresponder à cadeia de caracteres vazia.
O parâmetro catch-all faz o escape dos caracteres corretos quando a rota é usada para gerar uma URL,
incluindo os caracteres separadores de caminho ( / ). Por exemplo, a rota foo/{*path} com valores de rota
{ path = "my/path" } gera foo/my%2Fpath . Observe o escape da barra invertida. Para fazer a viagem de ida e
volta dos caracteres separadores de caminho, use o prefixo do parâmetro da rota ** . A rota foo/{**path}
com { path = "my/path" } gera foo/my/path .
Você pode usar o asterisco ( * ) como um prefixo para um parâmetro de rota a ser associado ao restante do
URI. Isso é chamado de parâmetro catch-all. Por exemplo, blog/{*slug} corresponde a qualquer URI que
começa com /blog e tem qualquer valor depois dele, que é atribuído ao valor de rota slug . Os parâmetros
catch-all também podem corresponder à cadeia de caracteres vazia.
O parâmetro catch-all faz o escape dos caracteres corretos quando a rota é usada para gerar uma URL,
incluindo os caracteres separadores de caminho ( / ). Por exemplo, a rota foo/{*path} com valores de rota
{ path = "my/path" } gera foo/my%2Fpath . Observe o escape da barra invertida.
Os parâmetros de rota podem ter valores padrão, designados pela especificação do valor padrão após o
nome do parâmetro separado por um sinal de igual ( = ). Por exemplo, {controller=Home} define Home
como o valor padrão de controller . O valor padrão é usado se nenhum valor está presente na URL para o
parâmetro. Os parâmetros de rota se tornam opcionais com o acréscimo de um ponto de interrogação ( ? )
ao final do nome do parâmetro, como em id? . A diferença entre valores opcionais e parâmetros de rota
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 um valor é fornecido pela URL de solicitação.
Os parâmetros de rota 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 ficarão 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 e os argumentos são passados 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 uma restrição minlength com o argumento 10 . Para obter mais
informações sobre as restrições de rota e uma lista das restrições fornecidas pela estrutura, confira a seção
Referência de restrição de rota.
Os parâmetros de rota também podem ter transformadores de parâmetro, que transformam o valor de um
parâmetro ao gerar links e fazer a correspondência de ações e páginas com URLs. Assim como as restrições,
os transformadores de parâmetro podem ser adicionados embutidos a um parâmetro de rota colocando
dois-pontos ( : ) e o nome do transformador após o nome do parâmetro de rota. Por exemplo, o modelo de
rota blog/{article:slugify} especifica um transformador slugify . Para obter mais informações sobre
transformadores de parâmetro, confira a seção Referência de transformador de parâmetro.
A tabela a seguir demonstra modelos de rota de exemplo 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.
TIP
Habilite o Log para ver como as implementações de roteamento internas, como Route , correspondem às solicitações.
WARNING
Não use restrições para a validação de entrada. Se as restrições forem usadas para a validação de entrada, uma
entrada inválida resultará em uma resposta 404 – Não Encontrado, em vez de 400 – Solicitação Inválida com uma
mensagem de erro apropriada. As restrições de rota são usadas para desfazer a ambiguidade entre rotas
semelhantes, não para validar as entradas de uma rota específica.
CORRESPONDÊNCIAS DE
RESTRIÇÃO EXEMPLO EXEMPLO OBSERVAÇÕES
Várias restrições delimitadas por vírgula podem ser aplicadas a um único parâmetro. Por exemplo, a restrição
a seguir restringe um parâmetro para um valor inteiro de 1 ou maior:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
WARNING
As restrições de rota que verificam a URL e são convertidas em um tipo CLR (como int ou DateTime ) sempre
usam a cultura invariável. Essas restrições consideram 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 são armazenados como cadeias de caracteres. Por exemplo, a restrição float tenta 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
ao construtor de
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
expressão regular. Confira 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. Para usar a expressão regular
^\d{3}-\d{2}-\d{4}$ no roteamento, a expressão precisa ter os caracteres \ (barra invertida) fornecidos na
cadeia de caracteres como caracteres \\ (barra invertida dupla) no arquivo de origem C# para fazer o
escape do caractere de escape da cadeia de caracteres \ (a menos que estejam sendo usados literais de
cadeia de caracteres textuais). Para fazer o escape dos caracteres de delimitador de parâmetro de roteamento
( { , } , [ , ] ), duplique os caracteres na expressão ( {{ , } , [[ , ]] ). A tabela a seguir mostra uma
expressão regular e a versão com escape.
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$
As expressões regulares usadas no roteamento geralmente começam com o caractere de acento circunflexo (
^ ) e correspondem à posição inicial da cadeia de caracteres. As expressões geralmente terminam com o
caractere de cifrão ( $ ) e correspondem ao final da cadeia de caracteres. Os caracteres ^ e $ garantem
que a expressão regular corresponde a todo o valor do parâmetro de rota. Sem os caracteres ^ e $ , a
expressão regular corresponde a qualquer subcadeia de caracteres na cadeia de caracteres, o que geralmente
não é o desejado. A tabela a seguir fornece exemplos e explica por que eles encontram ou não uma
correspondência.
Para saber mais sobre a sintaxe de expressões regulares, confira Expressões regulares do .NET Framework.
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)$)} apenas corresponde o valor da rota action a list , get ou
create . Se passada para o dicionário de restrições, a cadeia de caracteres ^(list|get|create)$ é
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.
services.AddRouting(options =>
{
options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
});
a restrição pode então ser aplicada às rotas da maneira usual, usando o nome especificado ao registrar o tipo
de restrição. Por exemplo:
[HttpGet("{id:customName}")]
public ActionResult<string> Get(string id)
Referência de parâmetro de transformador
Transformadores de parâmetro:
Executar ao gerar um link para um Route .
Implementar Microsoft.AspNetCore.Routing.IOutboundParameterTransformer .
São configurados usando ConstraintMap.
Usam o valor de rota do parâmetro e o transformam em um novo valor de cadeia de caracteres.
Resultam no uso do valor transformado no link gerado.
Por exemplo, um transformador de parâmetro slugify personalizado em padrão de rota
blog\{article:slugify} com Url.Action(new { article = "MyTestArticle" }) gera blog\my-test-article .
services.AddRouting(options =>
{
// Replace the type and the name used to refer to it with your own
// IOutboundParameterTransformer implementation
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
Os transformadores de parâmetro são usados pela estrutura para transformar o URI no qual um ponto de
extremidade é resolvido. Por exemplo, o ASP.NET Core MVC usa os transformadores de parâmetro para
transformar o valor de rota usado para corresponder a um area , controller , action e page .
routes.MapRoute(
name: "default",
template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
ASP.NET Core fornece convenções de API para usar transformadores de parâmetro com as rotas geradas:
ASP.NET Core MVC tem a convenção de API
Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention . Essa convenção aplica um
transformador de parâmetro especificado a todas as rotas de atributo no aplicativo. O transformador de
parâmetro transforma os tokens de rota do atributo conforme elas são substituídas. Para obter mais
informações, confira Usar um transformador de parâmetro para personalizar a substituição de token.
O Razor Pages tem a convenção de API
Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteTransformerConvention . Essa convenção aplica um
transformador de parâmetro especificado a todas as Razor Pages descobertas automaticamente. O
transformador de parâmetro transforma os segmentos de nome de arquivo e pasta de rotas do Razor
Pages. Para obter mais informações, confira Usar um transformador de parâmetros para personalizar
rotas de página.
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:
routes.MapRoute("blog_route", "blog/{*slug}",
defaults: new { controller = "Blog", action = "ReadPost" });
A geração de link somente gera um link para essa rota quando os valores correspondentes de controller e
action são fornecidos.
Segmentos complexos
Segmentos complexos (por exemplo, [Route("/x{token}y")] ) são processados por meio da combinação de
literais da direita para a esquerda, de uma maneira diferente de Greedy. Confira este código para ver uma
explicação detalhada de como os segmentos complexos são combinados. O exemplo de código não é usado
pelo ASP.NET Core, mas fornece uma explicação adequada sobre segmentos complexos.
Usar vários ambientes no ASP.NET Core
30/01/2019 • 15 minutes to read • Edit Online
Ambientes
O ASP.NET Core lê a variável de ambiente ASPNETCORE_ENVIRONMENT na inicialização do aplicativo e armazena
o valor em IHostingEnvironment.EnvironmentName. Você pode definir ASPNETCORE_ENVIRONMENT como
qualquer valor, mas há suporte para três valores na estrutura: Desenvolvimento, Preparo e Produção. Se
ASPNETCORE_ENVIRONMENT não estiver definido, o padrão será Production .
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
O código anterior:
Chama UseDeveloperExceptionPage quando ASPNETCORE_ENVIRONMENT é definido como Development .
Chama UseExceptionHandler quando o valor de ASPNETCORE_ENVIRONMENT é definido com um dos
seguintes:
Staging
Production
Staging_2
NOTE
A propriedade applicationUrl no launchSettings.json pode especificar uma lista de URLs de servidores. Use um
ponto e vírgula entre as URLs na lista:
"EnvironmentsSample": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
Quando o aplicativo é inicializado com dotnet run, o primeiro perfil com "commandName": "Project" é usado. O
valor de commandName especifica o servidor Web a ser iniciado. commandName pode ser qualquer um dos
seguintes:
IISExpress
IIS
Project (que inicia o Kestrel)
Quando um aplicativo for 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.
A saída a seguir mostra um aplicativo iniciado com dotnet run:
A guia Depurar das propriedades do projeto 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 ao seu ambiente.
WARNING
launchSettings.json não deve armazenar segredos. A ferramenta Secret Manager pode ser usado para armazenar
segredos de desenvolvimento local.
Ao usar Visual Studio Code, variáveis de ambiente podem ser definidas no arquivo .vscode/launch.json. O
exemplo a seguir define o ambiente como Development :
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
]
}
Um arquivo .vscode/launch.json do projeto não é lido ao iniciar o aplicativo com dotnet run da mesma
maneira que Properties/launchSettings.json. Ao inicializar um aplicativo em desenvolvimento que não tem
um arquivo launchSettings.json, defina o ambiente com uma variável de ambiente ou um argumento de linha
de comando para o comando dotnet run .
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 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.
Definir 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 desabilitará a maioria dos recursos de depuração. O método para configurar
o ambiente depende do sistema operacional.
Serviço de Aplicativo do Azure
Para definir o ambiente no Serviço de Aplicativo do Azure, execute as seguintes etapas:
1. Selecione o aplicativo na folha Serviços de Aplicativos.
2. No grupo CONFIGURAÇÕES, selecione a folha Configurações do aplicativo.
3. Na área Configurações do aplicativo, selecione Adicionar nova configuração.
4. Para Inserir um nome, forneça ASPNETCORE_ENVIRONMENT . Para Inserir um valor, fornecer o ambiente (por
exemplo, Staging ).
5. Marque a caixa de seleção Configuração do Slot se desejar que a configuração do ambiente permaneça
no slot atual quando os slots de implantação forem trocados. Para obter mais informações, confira
Documentação do Azure: que configurações são trocadas?.
6. Selecione Salvar na parte superior da folha.
O Serviço de Aplicativo do Azure reinicia automaticamente o aplicativo após uma configuração de aplicativo
(variável de ambiente) ser adicionada, alterada ou excluída no Portal do Azure.
Windows
Para definir o ASPNETCORE_ENVIRONMENT para a sessão atual quando o aplicativo for iniciado usando dotnet run,
os comandos a seguir serão usados:
Prompt de comando
set ASPNETCORE_ENVIRONMENT=Development
PowerShell
$Env:ASPNETCORE_ENVIRONMENT = "Development"
Esses comandos somente têm efeito para a janela atual. Quando a janela é fechada, a configuração
ASPNETCORE_ENVIRONMENT é revertida para a configuração padrão ou o valor de computador.
Para definir o valor globalmente no Windows, use uma das seguintes abordagens:
Abra o Painel de Controle > Sistema > Configurações avançadas do sistema e adicione ou edite
o valor ASPNETCORE_ENVIRONMENT :
Abra um prompt de comando administrativo e use o comando setx ou abra um prompt de comando
administrativo do PowerShell e use [Environment]::SetEnvironmentVariable :
Prompt de comando
O valor da opção Machine indica para definir a variável de ambiente no nível do sistema. Se o valor da
opção for alterado para User , a variável de ambiente será definida para a conta de usuário.
Quando a variável de ambiente ASPNETCORE_ENVIRONMENT é definida globalmente, ela entra em vigor para
dotnet run em qualquer janela de comando aberta depois que o valor é definido.
web.config
Para definir a variável de ambiente ASPNETCORE_ENVIRONMENT com web.config, consulte a seção Definindo
variáveis de ambiente de Módulo do ASP.NET Core. Quando a variável de ambiente ASPNETCORE_ENVIRONMENT
é definida com web.config, seu valor substitui uma configuração no nível do sistema.
Arquivo de projeto ou perfil de publicação
Para implantações do Windows IIS: Inclua a propriedade <EnvironmentName> no perfil de publicação
(.pubxml) ou no arquivo de projeto. Esta abordagem define o ambiente no arquivo web.config quando o
projeto é publicado:
<PropertyGroup>
<EnvironmentName>Development</EnvironmentName>
</PropertyGroup>
IMPORTANT
Ao hospedar um aplicativo no IIS e adicionar ou alterar a variável de ambiente ASPNETCORE_ENVIRONMENT , use
qualquer uma das abordagens a seguir para que o novo valor seja escolhido por aplicativos:
Execute net stop was /y seguido por net start w3svc em um prompt de comando.
Reinicie o servidor.
macOS
A configuração do ambiente atual para macOS pode ser feita em linha ao executar o 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. Adicione a seguinte instrução:
export ASPNETCORE_ENVIRONMENT=Development
Linux
Para distribuições Linux, use o comando export no prompt 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
Para carregar a configuração por ambiente, recomendamos:
Arquivos appsettings (*appsettings.<>.json). Confira Configuração: provedor de configuração do arquivo.
Variáveis de ambiente (definidas em cada sistema em que o aplicativo está hospedado). Confira
Configuração: provedor de configuração do arquivo e Armazenamento seguro de segredos do aplicativo
em desenvolvimento: variáveis de ambiente.
Gerenciador de Segredo (somente no ambiente de desenvolvimento). Consulte Armazenamento seguro
dos segredos do aplicativo em desenvolvimento no ASP.NET Core.
return WebHost.CreateDefaultBuilder(args)
.UseStartup(assemblyName);
}
public static void Main(string[] args)
{
CreateWebHost(args).Run();
}
return WebHost.CreateDefaultBuilder(args)
.UseStartup(assemblyName)
.Build();
}
host.Run();
}
}
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
app.UseExceptionHandler("/Error");
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
}
Recursos adicionais
Inicialização de aplicativo no ASP.NET Core
Configuração no ASP.NET Core
IHostingEnvironment.EnvironmentName
Configuração no ASP.NET Core
28/01/2019 • 66 minutes to read • Edit Online
Configuração padrão
Aplicativos Web baseados em modelos dotnet new do ASP.NET Core chamam CreateDefaultBuilder
ao criar um host. CreateDefaultBuilder fornece a configuração padrão para o aplicativo.
A configuração do host é fornecida de:
Variáveis de ambiente prefixadas com ASPNETCORE_ (por exemplo, ASPNETCORE_ENVIRONMENT )
usando o Provedor de Configuração de Variáveis de Ambiente.
Argumentos de linha de comando usando o Provedor de Configuração de Linha de
Comando.
A configuração do aplicativo é fornecida de (na seguinte ordem):
appsettings.json usando o Provedor de Configuração do Arquivo.
appsettings.{Environment}.json usando o Provedor de Configuração do Arquivo.
Gerenciador de Segredo quando o aplicativo é executado no ambiente Development usando
o assembly de entrada.
Variáveis de ambiente usando o Provedor de Configuração de Variáveis de Ambiente.
Argumentos de linha de comando usando o Provedor de Configuração de Linha de
Comando.
Os provedores de configuração são explicados posteriormente neste tópico. Para obter mais
informações sobre o host e CreateDefaultBuilder , consulte Host da Web do ASP.NET Core.
Segurança
Adote as melhores práticas a seguir:
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 usar vários ambientes e gerenciar o armazenamento seguro de segredos de
aplicativo em desenvolvimento com o Gerenciador de Segredo (inclui recomendações sobre como
usar variáveis de ambiente para armazenar dados confidenciais). O Gerenciador de Segredo usa o
Provedor de Configuração de Arquivo para armazenar segredos do usuário em um arquivo JSON no
sistema local. O Provedor de Configuração de Arquivo será descrito mais adiante neste tópico.
O Azure Key Vault é uma opção para o armazenamento seguro de segredos do aplicativo. Para obter
mais informações, consulte Provedor de configuração do Cofre de chaves do Azure no ASP.NET
Core.
Quando o arquivo é lido na configuração, ocorre a criação de chaves exclusivas para manter a
estrutura hierárquica de dados original da fonte de configuração. As seções e as chaves são niveladas
usando dois-pontos ( : ) para manter a estrutura original:
section0:key0
section0:key1
section1:key0
section1:key1
Os métodos GetSection e GetChildren estão disponíveis para isolar as seções e os filhos de uma
seção nos dados de configuração. Esses métodos serão descritos posteriormente em GetSection,
GetChildren e Exists. GetSection está no pacote Microsoft.Extensions.Configuration, que está no
metapacote Microsoft.AspNetCore.App.
Convenções
Na inicialização do aplicativo, as fontes de configuração são lidas na ordem especificada pelos
provedores de configuração.
Os Provedores de Configuração de Arquivo têm a capacidade de recarregar a configuração quando
um arquivo de configurações subjacente é alterado após a inicialização do aplicativo. O Provedor de
Configuração de Arquivo será descrito mais adiante neste tópico.
IConfiguration está disponível no contêiner DI (injeção de dependência) do aplicativo. Os provedores
de configuração não podem utilizar a DI, pois ela não é disponibilizada quando eles são configurados
pelo host.
As chaves de configuração adotam as convenções a seguir:
As chaves não diferenciam maiúsculas de minúsculas. Por exemplo, ConnectionString e
connectionstring são tratados como chaves equivalentes.
Se um valor para a mesma chave for definido pelos mesmos provedores de configuração, ou por
outros, o último valor definido na chave será o valor usado.
Chaves hierárquicas
Ao interagir com a API de configuração, um separador de dois-pontos ( : ) funciona em
todas as plataformas.
Nas variáveis de ambiente, talvez um separador de dois-pontos não funcione em todas as
plataformas. Um sublinhado duplo ( __ ) é compatível com todas as plataformas e é
convertido em dois-pontos.
No Azure Key Vault, as chaves hierárquicas usam -- (dois traços) como separador. Você
deve fornecer o código para substituir os traços por dois-pontos quando os segredos forem
carregados na configuração do aplicativo.
O ConfigurationBinder dá suporte a matrizes de associação para objetos usando os índices em
chaves de configuração. A associação de matriz está descrita na seção Associar uma matriz a uma
classe.
Os valores de configuração adotam as convenções a seguir:
Os valores são cadeias de caracteres.
Não é possível armazenar valores nulos na configuração ou associá-los a objetos.
Provedores
A tabela a seguir mostra os provedores de configuração disponíveis para aplicativos ASP.NET Core.
if (appAssembly != null)
{
builder.AddUserSecrets(appAssembly, optional: true);
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
ConfigureAppConfiguration
Chame ConfigureAppConfiguration ao criar o host para especificar os provedores de configuração do
aplicativo, além daqueles adicionados automaticamente por CreateDefaultBuilder:
public class Program
{
public static Dictionary<string, string> arrayDict = new Dictionary<string, string>
{
{"array:entries:0", "value0"},
{"array:entries:1", "value1"},
{"array:entries:2", "value2"},
{"array:entries:4", "value4"},
{"array:entries:5", "value5"}
};
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>();
}
}
Exemplo
O aplicativo de exemplo 2.x aproveita a vantagem do método de conveniência estático
CreateDefaultBuilder para criar o host, que inclui uma chamada para AddCommandLine.
Barra ( / ) /CommandLineKey3=value3 ,
/CommandLineKey3 value3
No mesmo comando, não combine pares chave-valor do argumento de linha de comando que usam
um sinal de igual com pares chave-valor que usam um espaço.
Exemplo de comandos:
Mapeamentos de comutador
Os mapeamentos de comutador permitem fornecer a lógica de substituição do nome da chave.
Quando você cria manualmente a configuração com um ConfigurationBuilder, pode fornecer um
dicionário de substituições de opções para o método AddCommandLine.
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 o par chave-valor na configuração do aplicativo. 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.
Chame ConfigureAppConfiguration ao criar o host para especificar a configuração do aplicativo:
Conforme mostrado no exemplo anterior, a chamada para CreateDefaultBuilder não deve passar
argumentos ao usar mapeamentos de opção. A chamada AddCommandLine do método
CreateDefaultBuilder não inclui opções mapeadas, e não é possível passar o dicionário de
mapeamento de opções para CreateDefaultBuilder . Se os argumentos incluírem uma opção
mapeada e forem passados para CreateDefaultBuilder , o provedor AddCommandLine não inicializará
com um FormatException. A solução não é passar os argumentos para CreateDefaultBuilder , mas,
em vez disso, permitir que o método AddCommandLine do método ConfigurationBuilder processe os
dois argumentos e o dicionário de mapeamento de opções.
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
Conforme mostrado no exemplo anterior, a chamada para CreateDefaultBuilder não deve passar
argumentos ao usar mapeamentos de opção. A chamada AddCommandLine do método
CreateDefaultBuilder não inclui opções mapeadas, e não é possível passar o dicionário de
mapeamento de opções para CreateDefaultBuilder . Se os argumentos incluírem uma opção
mapeada e forem passados para CreateDefaultBuilder , o provedor AddCommandLine não inicializará
com um FormatException. A solução não é passar os argumentos para CreateDefaultBuilder , mas,
em vez disso, permitir que o método AddCommandLine do método ConfigurationBuilder processe os
dois argumentos e o dicionário de mapeamento de opções.
using (host)
{
Console.ReadLine();
}
}
Depois que o dicionário de mapeamentos de comutador for criado, ele conterá os dados mostrados
na tabela a seguir.
CHAVE VALOR
-CLKey1 CommandLineKey1
-CLKey2 CommandLineKey2
Se as chaves mapeadas para opção forem usadas ao iniciar o aplicativo, a configuração receberá o
valor de configuração na chave fornecida pelo dicionário:
Após a execução do comando anterior, a configuração conterá os valores mostrados na tabela a seguir.
CHAVE VALOR
CommandLineKey1 value1
CommandLineKey2 value2
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>();
}
}
Exemplo
O aplicativo de exemplo 2.x aproveita a vantagem do método de conveniência estático
CreateDefaultBuilder para criar o host, que inclui uma chamada para AddEnvironmentVariables .
Se você quiser expor todas as variáveis de ambiente disponíveis para o aplicativo, altere o
FilteredConfiguration em Controllers/HomeController.cs para o seguinte:
FilteredConfiguration = _config.AsEnumerable();
Prefixos
Variáveis de ambiente carregadas na configuração do aplicativo são filtradas quando você fornece um
prefixo para o método AddEnvironmentVariables . Por exemplo, para filtrar as variáveis de ambiente no
prefixo CUSTOM_ , forneça o prefixo para o provedor de configuração:
MYSQLCONNSTR_ MySQL
Quando uma variável de ambiente for descoberta e carregada na configuração com qualquer um dos
quatro prefixos mostrados na tabela:
A chave de configuração é criada removendo o prefixo da variável de ambiente e adicionando uma
seção de chave de configuração ( ConnectionStrings ).
Um novo par chave-valor de configuração é criado para representar o provedor de conexão de
banco de dados (exceto para CUSTOMCONNSTR_ , que não tem um provedor indicado).
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>();
}
}
[section0]
key0=value
key1=value
[section1]
subsection:key=value
[section2:subsection0]
key=value
[section2:subsection1]
key=value
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>();
}
}
AllowedHosts * *
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>();
}
}
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>();
}
}
GetValue
ConfigurationBinder.GetValue<T> extrai um valor de configuração com uma chave especificada e o
converte para o tipo especificado. Uma sobrecarga permite que você forneça um valor padrão se a
chave não for encontrada.
O exemplo a seguir extrai o valor de cadeia de caracteres da configuração com a chave NumberKey ,
digita o valor como um int e armazena o valor na variável intValue . Se NumberKey não for
encontrado em chaves de configuração, intValue recebe o valor padrão de 99 :
{
"section0": {
"key0": "value",
"key1": "value"
},
"section1": {
"key0": "value",
"key1": "value"
},
"section2": {
"subsection0" : {
"key0": "value",
"key1": "value"
},
"subsection1" : {
"key0": "value",
"key1": "value"
}
}
}
Quando o arquivo é lido na configuração, as seguintes chaves hierárquicas exclusivas são criadas para
conter os valores de configuração:
section0:key0
section0:key1
section1:key0
section1:key1
section2:subsection0:key0
section2:subsection0:key1
section2:subsection1:key0
section2:subsection1:key1
GetSection
IConfiguration.GetSection extrai uma subseção de configuração com a chave de subseção
especificada. GetSection está no pacote Microsoft.Extensions.Configuration, que está no metapacote
Microsoft.AspNetCore.App.
Para retornar um IConfigurationSection que contenha apenas os pares chave-valor em section1 ,
chame GetSection e forneça o nome da seção:
GetSection nunca retorna null . Se uma seção correspondente não for encontrada, um
IConfigurationSection vazio será retornado.
Quando GetSection retorna uma seção correspondente, Value não é preenchido. Key e Path são
retornados quando a seção existe.
GetChildren
Uma chamada para IConfiguration.GetChildren em section2 obtém um
IEnumerable<IConfigurationSection> que inclui:
subsection0
subsection1
Exists
Use ConfigurationExtensions.Exists para determinar se há uma seção de configuração:
Considerando os dados de exemplo, sectionExists é false , pois não existe uma seção
section2:subsection2 nos dados de configuração.
A seção starship do arquivo starship.json cria a configuração quando o aplicativo de exemplo usa o
Provedor de Configuração JSON para carregar a configuração:
{
"starship": {
"name": "USS Enterprise",
"registry": "NCC-1701",
"class": "Constitution",
"length": 304.8,
"commissioned": false
},
"trademark": "Paramount Pictures Corp. http://www.paramount.com"
}
{
"starship": {
"name": "USS Enterprise",
"registry": "NCC-1701",
"class": "Constitution",
"length": 304.8,
"commissioned": false
},
"trademark": "Paramount Pictures Corp. http://www.paramount.com"
}
CHAVE VALOR
starship:registry NCC-1701
starship:class Constituição
starship:length 304,8
starship:commissioned False
O aplicativo de exemplo chama GetSection com a chave starship . Os pares chave-valor starship
são isolados. O método Bind é chamado na subseção passando uma instância da classe Starship .
Depois de associar os valores de instância, a instância é atribuída a uma propriedade para
renderização:
A configuração está associada ao método do grafo de objeto TvShow inteiro com o método Bind .A
instância associada é atribuída a uma propriedade para renderização:
TvShow = _config.GetSection("tvshow").Get<TvShow>();
viewModel.TvShow = _config.GetSection("tvshow").Get<TvShow>();
NOTE
A associação é fornecida por convenção. Provedores de configuração personalizados não são necessários para
implementar a associação de matriz.
CHAVE VALOR
array:entries:0 value0
array:entries:1 value1
array:entries:2 value2
array:entries:4 value4
array:entries:5 value5
Essas chaves e valores são carregados no aplicativo de exemplo usando o Provedor de Configuração
de Memória:
public class Program
{
public static Dictionary<string, string> arrayDict = new Dictionary<string, string>
{
{"array:entries:0", "value0"},
{"array:entries:1", "value1"},
{"array:entries:2", "value2"},
{"array:entries:4", "value4"},
{"array:entries:5", "value5"}
};
Configuration = builder.Build();
}
A matriz ignora um valor para o índice #3. O associador de configuração não é capaz de associar
valores nulos ou criar entradas nulas em objetos associados, o que fica claro em um momento
quando o resultado da associação dessa matriz a um objeto é demonstrado.
No aplicativo de exemplo, uma classe POCO está disponível para armazenar os dados de
configuração associados:
ArrayExample = _config.GetSection("array").Get<ArrayExample>();
viewModel.ArrayExample = _config.GetSection("array").Get<ArrayExample>();
0 value0
1 value1
2 value2
3 value4
4 value5
{
"array:entries:3": "value3"
}
No ConfigureAppConfiguration:
No construtor Startup :
CHAVE VALOR
array:entries:3 value3
Se a instância da classe ArrayExample for associada após o Provedor de Configuração JSON incluir a
entrada para o índice #3, a matriz ArrayExample.Entries incluirá o valor.
0 value0
1 value1
2 value2
3 value3
4 value4
5 value5
{
"json_array": {
"key": "valueA",
"subsection": [
"valueB",
"valueC",
"valueD"
]
}
}
CHAVE VALOR
json_array:key valueA
json_array:subsection:0 valueB
json_array:subsection:1 valueC
json_array:subsection:2 valueD
No aplicativo de exemplo, a seguinte classe POCO está disponível para associar os pares chave-valor
de configuração:
0 valueB
1 valueC
2 valueD
OptionsAction(builder);
Data = !dbContext.Values.Any()
? CreateAndSaveDefaultValues(dbContext)
: dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
}
}
dbContext.Values.AddRange(configValues
.Select(kvp => new EFConfigurationValue
{
Id = kvp.Key,
Value = kvp.Value
})
.ToArray());
dbContext.SaveChanges();
return configValues;
}
}
public class EFConfigurationProvider : ConfigurationProvider
{
public EFConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}
OptionsAction(builder);
Data = !dbContext.Values.Any()
? CreateAndSaveDefaultValues(dbContext)
: dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
}
}
dbContext.Values.AddRange(configValues
.Select(kvp => new EFConfigurationValue
{
Id = kvp.Key,
Value = kvp.Value
})
.ToArray());
dbContext.SaveChanges();
return configValues;
}
}
Extensions/EntityFrameworkExtensions.cs:
public static class EntityFrameworkExtensions
{
public static IConfigurationBuilder AddEFConfiguration(
this IConfigurationBuilder builder,
Action<DbContextOptionsBuilder> optionsAction)
{
return builder.Add(new EFConfigurationSource(optionsAction));
}
}
Configuration = builder.Build();
}
@page
@model IndexModel
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
<!DOCTYPE html>
<html lang="en">
<head>
<title>Index Page</title>
</head>
<body>
<h1>Access configuration in a Razor Pages page</h1>
<p>Configuration value for '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 value for 'key': @Configuration["key"]</p>
</body>
</html>
Recursos adicionais
Padrão de opções no ASP.NET Core
Detalhes da configuração da Microsoft
Padrão de opções no ASP.NET Core
16/01/2019 • 21 minutes to read • Edit Online
Pré-requisitos
Referencie o metapacote Microsoft.AspNetCore.App ou adicione uma referência de pacote ao pacote
Microsoft.Extensions.Options.ConfigurationExtensions.
Interfaces de opções
O IOptionsMonitor<TOptions> é usado para recuperar as opções e gerenciar notificações de opções para
instâncias de TOptions . O IOptionsMonitor<TOptions> dá suporte aos seguintes cenários:
Notificações de alteração
Opções nomeadas
Configuração recarregável
Invalidação seletiva de opções (IOptionsMonitorCache<TOptions>)
Os cenários de Pós-configuração permitem que você defina ou altere as opções depois de todas as
configurações do IConfigureOptions<TOptions> serem feitas.
O IOptionsFactory<TOptions> é responsável por criar novas instâncias de opções. Ele tem um único método
Create. A implementação padrão usa todos os IConfigureOptions<TOptions> e
IPostConfigureOptions<TOptions> registrados e executa todas as configurações primeiro, seguidas da pós-
configuração. Ela faz distinção entre IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> e
chama apenas a interface apropriada.
O IOptionsMonitorCache<TOptions> é usado pelo IOptionsMonitor<TOptions> para armazenar em cache as
instâncias do TOptions . O IOptionsMonitorCache<TOptions> invalida as instâncias de opções no monitor, de
modo que o valor seja recalculado (TryRemove). Os valores podem ser manualmente inseridos com TryAdd. O
método Clear é usado quando todas as instâncias nomeadas devem ser recriadas sob demanda.
O IOptionsSnapshot<TOptions> é útil em cenários em que as opções devam ser novamente computadas em
cada solicitação. Para saber mais, consulte a seção Recarregar dados de configuração com IOptionsSnapshot.
O IOptions<TOptions> pode ser usado para dar suporte a opções. No entanto, o IOptions<TOptions> não dá
suporte a cenários anteriores do IOptionsMonitor<TOptions>. Você pode continuar a usar o
IOptions<TOptions> em estruturas e bibliotecas existentes que já usam a interface do IOptions<TOptions> e
não exigem os cenários fornecidos pelo IOptionsMonitor<TOptions>.
public IndexModel(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
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:
NOTE
Ao usar um ConfigurationBuilder personalizado para carregar opções de configuração de um arquivo de configurações,
confirme se o caminho de base está corretamente configurado:
services.Configure<MyOptions>(config);
Não é necessário configurar o caminho de base explicitamente ao carregar opções de configuração do arquivo de
configurações por meio do CreateDefaultBuilder.
Index.cshtml.cs:
public IndexModel(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
Adicione vários provedores de configuração. Os provedores de configuração estão disponíveis nos pacotes do
NuGet e são aplicados na ordem em que são registrados. Para obter mais informações, consulte Configuração
no ASP.NET Core.
Cada chamada à Configure adiciona um serviço IConfigureOptions<TOptions> ao contêiner de serviço. No
exemplo anterior, os valores Option1 e Option2 são especificados em appsettings.json, mas os valores
Option1 e Option2 são substituídos pelo delegado configurado.
Quando mais de um serviço de configuração é habilitado, a última fonte de configuração especificada vence e
define o valor de configuração. Quando o aplicativo é executado, o método OnGet do modelo de página
retorna uma cadeia de caracteres que mostra os valores da classe de opção:
Configuração de subopções
A configuração de subopções é demonstrada no Exemplo #3 no aplicativo de exemplo.
Os aplicativos devem criar classes de opções que pertencem a grupos de cenários 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
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
A classe MySubOptions define as propriedades SubOption1 e SubOption2 , para armazenar os valores de opções
(Models/MySubOptions.cs):
O método OnGet do modelo da página retorna uma cadeia de caracteres com os valores de opções
(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(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
O aplicativo de exemplo mostra como injetar IOptionsMonitor<MyOptions> com uma diretiva @inject :
@page
@model IndexModel
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<MyOptions> OptionsAccessor
@{
ViewData["Title"] = "Options Sample";
}
<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(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_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";
});
API OptionsBuilder
OptionsBuilder<TOptions> é usada para configurar instâncias TOptions . OptionsBuilder simplifica a criação
de opções nomeadas, pois é apenas um único parâmetro para a chamada
AddOptions<TOptions>(string optionsName) inicial, em vez de aparecer em todas as chamadas subsequentes. A
validação de opções e as sobrecargas ConfigureOptions que aceitam dependências de serviço só estão
disponíveis por meio de OptionsBuilder .
services.AddOptions<MyOptions>("optionalName")
.Configure(o => o.Property = "named");
services.AddOptions<MyOptions>("optionalName")
.Configure<Service1, Service2, Service3, Service4, Service5>(
(o, s, s2, s3, s4, s5) =>
o.Property = DoSomethingWith(s, s2, s3, s4, s5));
Validação de opções
A validação de opções permite validar as opções depois de configurá-las. Chame Validate com um método de
validação que retorna true quando as opções são válidas, e false quando não são:
// Registration
services.AddOptions<MyOptions>("optionalOptionsName")
.Configure(o => { }) // Configure the options
.Validate(o => YourValidationShouldReturnTrueIfValid(o),
"custom error");
// Consumption
var monitor = services.BuildServiceProvider()
.GetService<IOptionsMonitor<MyOptions>>();
try
{
var options = monitor.Get("optionalOptionsName");
}
catch (OptionsValidationException e)
{
// e.OptionsName returns "optionalOptionsName"
// e.OptionsType returns typeof(MyOptions)
// e.Failures returns a list of errors, which would contain
// "custom error"
}
O exemplo anterior define a instância de opções nomeadas como optionalOptionsName . A instância de opções
padrão é Options.DefaultName .
A validação é executada após a criação da instância de opções. A instância de opções tem garantia de passar
pela validação, na primeira vez em que for acessada.
IMPORTANT
A validação não protege contra modificações de opções, depois que as opções são inicialmente configuradas e validadas.
IValidateOptions valida:
Uma instância específica de opções nomeadas.
Todas as opções quando name for null .
Retornar um ValidateOptionsResult da implementação da interface:
[Fact]
public void CanValidateDataAnnotations()
{
var services = new ServiceCollection();
services.AddOptions<AnnotatedOptions>()
.Configure(o =>
{
o.StringLength = "111111";
o.IntRange = 10;
o.Custom = "nowhere";
})
.ValidateDataAnnotations();
var sp = services.BuildServiceProvider();
A validação adiantada (fail fast na inicialização) está sendo considerada para uma versão futura.
Pós-configuração de opções
Defina a pós-configuração com IPostConfigureOptions<TOptions>. A pós-configuração é executada depois
que toda o configuração de IConfigureOptions<TOptions> é feita:
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Recursos adicionais
Configuração no ASP.NET Core
Registro em log no ASP.NET Core
04/02/2019 • 47 minutes to read • Edit Online
Adicionar provedores
Um provedor de log exibe ou armazena logs. Por exemplo, o provedor de Console exibe os logs no console, e o
provedor do Azure Application Insights armazena-os no Azure Application Insights. Os logs podem ser enviados
para vários destinos por meio da adição de vários provedores.
Para adicionar um provedor, chame o método de extensão Add{provider name} do provedor no Program.cs:
webHost.Run();
}
O modelo de projeto padrão chama o método de extensão CreateDefaultBuilder, que adiciona os seguintes
provedores de log:
Console
Depurar
EventSource (a partir do ASP.NET Core 2.2)
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
Se você usar CreateDefaultBuilder , poderá substituir os provedores padrão por aqueles que preferir. Chame
ClearProviders e adicione os provedores desejados.
host.Run();
}
Para usar um provedor, instale o pacote NuGet e chame o método de extensão do provedor em uma instância de
ILoggerFactory:
NOTE
O aplicativo de exemplo adiciona provedores de log no método Startup.Configure . Para obter saída de log do código que
é executado anteriormente, adicione provedores de log no construtor de classe Startup .
Saiba mais sobre provedores de log internos e provedores de log de terceiros mais adiante no artigo.
Criar logs
Obtenha um objeto ILogger<TCategoryName> da DI.
O exemplo de controlador a seguir cria os logs Information e Warning . A categoria é
TodoApiSample.Controllers.TodoController (o nome de classe totalmente qualificado do TodoController no
aplicativo de exemplo):
O exemplo do Razor Pages a seguir cria logs com Information como o nível e TodoApiSample.Pages.AboutModel
como a categoria:
O exemplo acima cria logs com Information e Warning como o nível e a classe TodoController como a categoria.
O nível de log indica a gravidade do evento registrado. A categoria do log é uma cadeia de caracteres associada a
cada log. A instância ILogger<T> cria logs que têm o nome totalmente qualificado do tipo T como a categoria.
Níveis e categorias serão explicados com mais detalhes posteriormente neste artigo.
Criar logs na inicialização
Para gravar logs na classe Startup , inclua um parâmetro ILogger na assinatura de construtor:
public class Startup
{
private readonly ILogger _logger;
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
}
host.Run();
}
Configuração
A configuração do provedor de logs é fornecida por um ou mais provedores de sincronização:
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).
Por exemplo, a configuração de log geralmente é fornecida pela seção Logging dos arquivos de configurações do
aplicativo. O exemplo a seguir mostra o conteúdo de um típico arquivo appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
},
"Console":
{
"IncludeScopes": true
}
}
}
A propriedade Logging pode ter LogLevel e propriedades do provedor de logs (o Console é mostrado).
A propriedade LogLevel em Logging especifica o nível mínimo para log nas categorias selecionadas. No exemplo,
as categorias System e Microsoft têm log no nível Information , e todas as outras no nível Debug .
Outras propriedades em Logging especificam provedores de logs. O exemplo se refere ao provedor de Console.
Se um provedor oferecer suporte a escopos de log, IncludeScopes indicará se eles estão habilitados. Uma
propriedade de provedor (como Console , no exemplo) também pode especificar uma propriedade LogLevel .
LogLevel em um provedor especifica os níveis de log para esse provedor.
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
Chaves LogLevel representam nomes de log. A chave Default aplica-se a logs não listados de forma explícita. O
valor representa o nível de log aplicado ao log fornecido.
Saiba mais sobre como implementar provedores de configuração em Configuração no ASP.NET Core.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/api/todo/0
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (0) -
ModelState is Valid
info: TodoApi.Controllers.TodoController[1002]
Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
GetById(0) NOT FOUND
info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
Executing HttpStatusCodeResult, setting HTTP status code 404
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action TodoApi.Controllers.TodoController.GetById (TodoApi) in 42.9286ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 148.889ms 404
Os logs anteriores foram gerados por meio de uma solicitação HTTP Get para o aplicativo de exemplo em
http://localhost:5000/api/todo/0 .
Veja um exemplo de como os mesmos logs aparecem na janela Depuração quando você executa o aplicativo de
exemplo no Visual Studio:
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET
http://localhost:53104/api/todo/0
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method
TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (0) - ModelState is Valid
TodoApi.Controllers.TodoController:Information: Getting item 0
TodoApi.Controllers.TodoController:Warning: GetById(0) NOT FOUND
Microsoft.AspNetCore.Mvc.StatusCodeResult:Information: Executing HttpStatusCodeResult, setting HTTP status code
404
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action
TodoApi.Controllers.TodoController.GetById (TodoApi) in 152.5657ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 316.3195ms 404
Os logs criados pelas chamadas ILogger mostradas na seção anterior começam com
"TodoApi.Controllers.TodoController". Os logs que começam com categorias "Microsoft" são de código da
estrutura ASP.NET Core. O ASP.NET Core e o código do aplicativo estão usando a mesma API de registro em log
e os mesmos provedores.
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
Quando um objeto ILogger é criado, uma categoria é especificada para ele. Essa categoria é incluída em cada
mensagem de log criada por essa instância de Ilogger . A categoria pode ser qualquer cadeia de caracteres, mas a
convenção é usar o nome da classe, como "TodoApi.Controllers.TodoController".
Use ILogger<T> para obter uma instância ILogger que usa o nome de tipo totalmente qualificado do T como a
categoria:
Nível de log
Todo log especifica um valor LogLevel. O nível de log indica a gravidade ou importância. Por exemplo, você pode
gravar um log Information quando um método é finalizado normalmente e um log Warning quando um método
retorna um código de status 404 Não Encontrado.
O código a seguir cria os logs Information e Warning :
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.
Use 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, envie Trace por meio do nível Information para um armazenamento de dados com volume.
Envie Warning por meio de Critical para um armazenamento de dados com valor.
Durante o desenvolvimento, envie Warning por meio Critical para o console e adicione Trace por meio de
Information ao solucionar problemas.
A seção Filtragem de log mais adiante neste artigo explicará como controlar os níveis de log que um provedor
manipula.
O ASP.NET Core grava logs para eventos de estrutura. Os exemplos de log anteriores neste artigo excluíram logs
abaixo do nível Information , portanto, logs de nível Debug ou Trace não foram criados. Veja um exemplo de logs
de console produzidos por meio da execução do aplicativo de exemplo configurado para mostrar logs Debug :
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
Cada log pode especificar uma ID do evento. O aplicativo de exemplo faz isso usando uma classe LoggingEvents
definida localmente:
Uma ID de evento associa um conjunto de eventos. Por exemplo, todos os logs relacionados à exibição de uma lista
de itens em uma página podem ser 1001.
O provedor de logs pode armazenar a ID do evento em um campo de ID na mensagem de log ou não armazenar.
O provedor de Depuração não mostra IDs de eventos. O provedor de console mostra IDs de evento entre colchetes
após a categoria:
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. No código a seguir, observe que os nomes de parâmetro estão fora de sequência no modelo de
mensagem:
string p1 = "parm1";
string p2 = "parm2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);
Esse código cria uma mensagem de log com os valores de parâmetro na sequência:
A estrutura de registros funciona dessa maneira para que os provedores de logs possam implementar registro em
log semântico, também conhecido como registro em log estruturado. Os próprios argumentos são passados para o
sistema de registro em log, não apenas o modelo de mensagem formatado. Essas informações permitem que os
provedores de log armazenem os valores de parâmetro como campos. Por exemplo, suponha que as chamadas de
método do agente sejam assim:
Se você estiver enviando os logs para o Armazenamento de Tabelas do Azure, cada entidade da Tabela do Azure
poderá ter propriedades ID e RequestTime , o que simplificará as consultas nos dados de log. Uma consulta pode
encontrar todos os logs em determinado intervalo de RequestTime sem 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
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.
Para suprimir todos os logs, especifique LogLevel.None como o nível de log mínimo. O valor inteiro de
LogLevel.None é 6, que é maior do que LogLevel.Critical (5 ).
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": {
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"IncludeScopes": false,
"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 para
todos os provedores. Apenas uma regra é escolhida para cada provedor quando um objeto ILogger é criado.
Regras de filtro no código
O exemplo a seguir mostra como registrar regras de filtro no código:
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 um objeto ILogger é criado, o objeto ILoggerFactory seleciona uma única regra por provedor para
aplicar a esse agente. Todas as mensagens gravadas pela instância 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 correspondência 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 correspondência 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 .
Com a lista anterior de regras, suponha que você crie 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.
A instância ILogger resultante envia logs de nível Trace e superior para o provedor Depuração. Logs de nível
Debug e superior são enviados para o provedor Console.
Aliases de provedor
Cada provedor define um alias que pode ser usado na configuração no lugar do nome de tipo totalmente
qualificado. 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
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. Por
exemplo:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logBuilder =>
{
logBuilder.AddFilter((provider, category, logLevel) =>
{
if (provider == "Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider" &&
category == "TodoApiSample.Controllers.TodoController")
{
return false;
}
return true;
});
})
.Build();
Alguns provedores de log permitem especificar quando os logs devem ser gravados em uma mídia de
armazenamento ou ignorados, com base no nível de log e na categoria.
Os métodos de extensão AddConsole e AddDebug fornecem sobrecargas que aceitam critérios de filtragem. O
código de exemplo a seguir faz com que o provedor de console ignore os logs abaixo do nível Warning , enquanto o
provedor Depuração ignora os logs criados pela estrutura.
O método AddEventLog tem uma sobrecarga que recebe uma instância EventLogSettings , que pode conter uma
função de filtragem em sua propriedade Filter . O provedor TraceSource não fornece nenhuma dessas
sobrecargas, porque seu nível de registro em log e outros parâmetros são baseados no SourceSwitch e no
TraceListener que ele usa.
Para definir regras de filtragem para todos os provedores registrados com uma instância ILoggerFactory , use o
método de extensão WithFilter . O exemplo abaixo limita os logs de estrutura (categoria começa com "Microsoft"
ou "Sistema") a avisos, enquanto registra em nível de depuração os logs criados por código de aplicativo.
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory
.WithFilter(new FilterLoggerSettings
{
{ "Microsoft", LogLevel.Warning },
{ "System", LogLevel.Warning },
{ "ToDoApi", LogLevel.Debug }
})
.AddConsole()
.AddDebug();
Para impedir a gravação de logs, especifique LogLevel.None como o nível de log mínimo. O valor inteiro de
LogLevel.None é 6, que é maior do que LogLevel.Critical (5 ).
CATEGORIA OBSERVAÇÕES
Escopos de log
Um escopo pode agrupar um conjunto de operações lógicas. Esse agrupamento pode ser usado para anexar os
mesmos dados para cada log criado como parte de um conjunto. Por exemplo, todo log criado como parte do
processamento de uma transação pode incluir a ID da transação.
Um escopo é um tipo IDisposable retornado pelo método BeginScope e que dura até que seja descartado. Use
um escopo por meio do encapsulamento de chamadas de agente em um bloco using :
NOTE
A configuração da opção de agente de console IncludeScopes é necessária para habilitar o registro em log baseado em
escopo.
Saiba mais sobre como configurar na seção Configuração.
Program.cs:
NOTE
A configuração da opção de agente de console IncludeScopes é necessária para habilitar o registro em log baseado em
escopo.
Startup.cs:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory
.AddConsole(includeScopes: true)
.AddDebug();
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
logging.AddConsole();
loggerFactory.AddConsole();
As sobrecargas do AddConsole permitem que você passe um nível de log mínimo, uma função de filtro e um valor
booliano que indica se escopos são compatíveis. Outra opção é passar um objeto IConfiguration , que pode
especificar suporte de escopos e níveis de log.
O provedor de console tem um impacto significativo no desempenho e geralmente não é adequado para uso em
produção.
Quando você cria um novo projeto no Visual Studio, o método AddConsole tem essa aparência:
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
As configurações mostradas limitam os logs de estrutura a avisos, permitindo que o aplicativo para faça registros
no nível de depuração, conforme explicado na Filtragem de log seção. Para obter mais informações, consulte
Configuração.
Para ver a saída de registro em log de console, abra um prompt de comando na pasta do projeto e execute o
seguinte comando:
dotnet run
Depurar provedor
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.
logging.AddDebug();
loggerFactory.AddDebug();
As sobrecargas de AddDebug permitem que você passe um nível de log mínimo ou uma função de filtro.
Provedor EventSource
Para aplicativos que se destinam ao ASP.NET Core 1.1.0 ou posterior, 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.
logging.AddEventSourceLogger();
loggerFactory.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).
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.
logging.AddEventLog();
loggerFactory.AddEventLog();
As sobrecargas de AddEventLog permitem que você passe EventLogSettings ou um nível de log mínimo.
Provedor TraceSource
O pacote de provedor Microsoft.Extensions.Logging.TraceSource usa as bibliotecas e provedores de TraceSource.
logging.AddTraceSource(sourceSwitchName);
loggerFactory.AddTraceSource(sourceSwitchName);
logging.AddAzureWebAppDiagnostics();
loggerFactory.AddAzureWebAppDiagnostics();
Recursos adicionais
Registro em log de alto desempenho com o LoggerMessage no ASP.NET Core
Tratar erros no ASP.NET Core
08/01/2019 • 14 minutes to read • Edit Online
Para configurar um aplicativo para exibir uma página que mostra informações detalhadas sobre exceções, use a
página de exceção do desenvolvedor. A página é disponibilizada pelo pacote Microsoft.AspNetCore.Diagnostics,
que está disponível no metapacote Microsoft.AspNetCore.All. Adicione uma linha ao método Startup.Configure
:
Para configurar um aplicativo para exibir uma página que mostra informações detalhadas sobre exceções, use a
página de exceção do desenvolvedor. A página é disponibilizada adicionando uma referência de pacote para o
pacote Microsoft.AspNetCore.Diagnostics no arquivo de projeto. Adicione uma linha ao método
Startup.Configure :
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}
Coloque a chamada para UseDeveloperExceptionPage na frente de qualquer middleware no qual você deseja
capturar exceções, tais como app.UseMvc .
WARNING
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:
Se a solicitação tem cookies, eles serão exibidos na guia Cookies. Os cabeçalhos são vistos na última guia:
Configurar uma página de tratamento de exceção personalizada
Configure uma página do manipulador de exceção para usar quando o aplicativo não estiver em execução no
ambiente Development :
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}
Em um aplicativo Razor Pages, o modelo dotnet new fornece uma página de erro e uma classe de erro
PageModel , na pasta Páginas do Razor Pages.
Em um aplicativo MVC, não decore o método de ação do manipulador de erro com atributos de método HTTP,
como HttpGet . Verbos explícitos impedem algumas solicitações de chegar ao método. Permita acesso anônimo
ao método para que os usuários não autenticados possam capazes receber a exibição de erro.
Por exemplo, o seguinte método de manipulador de erro é fornecido pelo modelo MVC dotnet novo e aparece
no controlador Home:
[AllowAnonymous]
public IActionResult Error()
{
return View(new ErrorViewModel
{ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
app.UseStatusCodePages();
UseStatusCodePages deve ser chamado antes dos middlewares de tratamento da solicitação no pipeline (por
exemplo, o middleware de arquivos estáticos e o middleware MVC ).
Por padrão, o middleware de páginas de código de status adiciona manipuladores, somente de texto, para os
códigos de status comuns, como o 404:
O middleware permite vários métodos de extensão. Um método usa uma expressão lambda:
await context.HttpContext.Response.WriteAsync(
"Status code page, status code: " +
context.HttpContext.Response.StatusCode);
});
Uma sobrecarga de UseStatusCodePages usa uma cadeia de caracteres de formato e de tipo de conteúdo:
app.UseStatusCodePages("text/plain", "Status code page, status code: {0}");
app.UseStatusCodePagesWithRedirects("/error/{0}");
UseStatusCodePagesWithReExecute:
Retorna o código de status original ao cliente.
Especifica que o corpo da resposta deve ser gerado, executando novamente o pipeline de solicitação por meio
de um caminho alternativo.
O modelo pode incluir um espaço reservado de {0} para o código de status. O modelo deve começar com uma
barra ( / ).
app.UseStatusCodePagesWithReExecute("/error/{0}");
As páginas de código de status podem ser desabilitadas para solicitações específicas em um método de
manipulador de Páginas Razor ou em um controlador do MVC. Para desabilitar as páginas de código de status,
tente recuperar o IStatusCodePagesFeature da coleção HttpContext.Features da solicitação e desabilitar o
recurso se ele estiver disponível:
if (statusCodePagesFeature != null)
{
statusCodePagesFeature.Enabled = false;
}
Para usar uma sobrecarga UseStatusCodePages* que aponta para um ponto de extremidade dentro do aplicativo,
crie um modo de exibição do MVC ou Razor Page para o ponto de extremidade. Por exemplo, o modelo dotnet
novo para um aplicativo Razor Pages produz a seguinte página e classe de modelo de página:
Error.cshtml:
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed
information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications
</strong>, as it can result in sensitive information from exceptions being
displayed to end users. For local debugging, development environment can be
enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment
variable to <strong>Development</strong>, and restarting the application.
</p>
Error.cshtml.cs:
TIP
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 no geral e use filtros apenas quando precisar
fazer o tratamento de erro de modo diferente, dependendo da ação do MVC escolhida.
Recursos adicionais
Referência de erros comuns para o Serviço de Aplicativo do Azure e o IIS com o ASP.NET Core
Solucionar problemas do ASP.NET Core no IIS
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Middleware do ASP.NET Core
17/01/2019 • 20 minutes to read • Edit Online
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
causar um curto -circuito do pipeline de solicitação. 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 são 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.
WARNING
Não chame next.Invoke depois que a resposta tiver sido enviada ao cliente. Altera para HttpResponse depois
de a resposta ser iniciada e lança uma exceção. Por exemplo, mudanças como a configuração de cabeçalhos e o
código de status lançam 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.
HasStarted é uma dica útil para indicar se os cabeçalhos foram enviados ou o corpo foi gravado.
Pedido
A ordem em que os componentes do middleware são adicionados ao método Startup.Configure
define a ordem em que os componentes de middleware são invocados nas solicitações e a ordem
inversa para a resposta. A ordem é crítica para a segurança, o desempenho e a funcionalidade.
O método Startup.Configure a seguir adiciona componentes de middleware para cenários de
aplicativo comuns:
1. Exceção/tratamento de erro
2. Protocolo HTTP Strict Transport Security
3. Redirecionamento para HTTPS
4. Servidor de arquivos estático
5. Imposição de política de cookies
6. Autenticação
7. Session
8. MVC
// If the app uses session state, call Session Middleware after Cookie
// Policy Middleware and before MVC Middleware.
app.UseSession();
1. Exceção/tratamento de erro
2. Arquivos estáticos
3. Autenticação
4. Session
5. MVC
public void Configure(IApplicationBuilder app)
{
// Enable the Exception Handler Middleware to catch exceptions
// thrown in the following middlewares.
app.UseExceptionHandler("/Home/Error");
app.Map("/map2", HandleMapTest2);
SOLICITAÇÃO RESPOSTA
SOLICITAÇÃO RESPOSTA
Map também pode ser correspondido com vários segmentos de uma vez:
public class Startup
{
private static void HandleMultiSeg(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map multiple segments.");
});
}
Middleware interno
O ASP.NET Core é fornecido com os seguintes componentes de middleware. A coluna Ordem fornece
observações sobre o posicionamento do middleware no pipeline de solicitação e sob quais condições o
middleware podem encerrar a solicitação e impedir que outro middleware processe uma solicitação.
Cabeçalhos encaminhados Encaminha cabeçalhos como proxy Antes dos componentes que
para a solicitação atual. consomem os campos atualizados.
Exemplos: esquema, host, IP do
cliente e método.
Substituição do Método HTTP Permite que uma solicitação de Antes dos componentes que
entrada POST substitua o método. consomem o método atualizado.
Segurança de Transporte Estrita de Middleware de aprimoramento de Antes das respostas serem enviadas
HTTP (HSTS) segurança que adiciona um e depois dos componentes que
cabeçalho de resposta especial modificam solicitações. Exemplos:
(ASP.NET Core 2.1 ou posterior). cabeçalhos encaminhados,
regravação de URL.
Cache de resposta Fornece suporte para as respostas Antes dos componentes que
em cache. exigem armazenamento em cache.
Regravação de URL Fornece suporte para regravar Antes dos componentes que
URLs e redirecionar solicitações. consomem a URL.
Gravar middleware
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 de uma 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;
}
}
}
namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
O nome do método Task de middleware precisa ser Invoke . No ASP.NET Core 2.0 ou posterior, o
nome pode ser Invoke ou InvokeAsync .
O seguinte método de extensão expõe o middleware por meio do IApplicationBuilder:
using Microsoft.AspNetCore.Builder;
namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}
}
}
O 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 se você precisar compartilhar serviços com middleware dentro de uma
solicitação.
Os componentes de middleware podem resolver suas dependências, utilizando a DI (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:
Recursos adicionais
Migrar módulos e manipuladores HTTP para middleware do ASP.NET Core
Inicialização de aplicativo no ASP.NET Core
Solicitar recursos no ASP.NET Core
Ativação de middleware baseada em alocador no ASP.NET Core
Ativação de middleware com um contêiner de terceiros no ASP.NET Core
Host da Web e Host Genérico no ASP.NET Core
12/12/2018 • 2 minutes to read • Edit Online
Aplicativos ASP.NET Core configuram e inicializam um host. O host é responsável pelo gerenciamento de
tempo de vida e pela inicialização do aplicativo. Duas APIs de host estão disponíveis para uso:
Host da Web – Adequado para a hospedagem de aplicativos Web.
Host Genérico (ASP.NET Core 2.1 ou posteriores) – Adequado para a hospedagem de aplicativos não Web
(por exemplo, aplicativos que executam tarefas em segundo plano). Em uma versão futura, o Host Genérico
será adequado para hospedar qualquer tipo de aplicativo, incluindo aplicativos Web. Eventualmente, o Host
Genérico substituirá o Host da Web.
Para hospedar aplicativos Web do ASP.NET Core, os desenvolvedores devem usar o Web Host com base em
IWebHostBuilder. Para hospedar aplicativos que não sejam Web, os desenvolvedores devem usar o Host
Genérico com base em HostBuilder.
Tarefas em segundo plano com serviços hospedados no ASP.NET Core
Aprenda a implementar tarefas em segundo plano com serviços hospedados no ASP.NET Core.
Usar assemblies de inicialização de hospedagem no ASP.NET Core
Descubra como aprimorar um aplicativo ASP.NET Core por meio de um assembly referenciado ou não
referenciado usando uma implementação de IHostingStartup.
Host da Web do ASP.NET Core
10/01/2019 • 28 minutes to read • Edit Online
Configurar um host
Crie um host usando uma instância do IWebHostBuilder. 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:
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true);
})
...
A seguinte chamada de ConfigureLogging adiciona um delegado para configurar o nível de log mínimo
(SetMinimumLevel) como LogLevel.Warning. Essa configuração substitui as configurações em
appsettings.Development.json ( LogLevel.Debug ) e appsettings.Production.json ( LogLevel.Error )
configuradas por CreateDefaultBuilder . ConfigureLogging pode ser chamado várias vezes.
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Warning);
})
...
WebHost.CreateDefaultBuilder(args)
.ConfigureKestrel((context, options) =>
{
options.Limits.MaxRequestBodySize = 20000000;
});
WebHost.CreateDefaultBuilder(args)
.UseKestrel(options =>
{
options.Limits.MaxRequestBodySize = 20000000;
});
A raiz do conteúdo determina onde o host procura por arquivos de conteúdo, como arquivos de exibição do
MVC. Quando o aplicativo é iniciado na pasta raiz do projeto, essa pasta é usada como a raiz do conteúdo. Esse
é o padrão usado no Visual Studio e nos novos modelos dotnet.
Para obter mais informações sobre a configuração de aplicativo, veja Configuração no ASP.NET Core.
NOTE
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 de 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)
.UseSetting(WebHostDefaults.ApplicationKey, "CustomApplicationName")
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.
WebHost.CreateDefaultBuilder(args)
.UseContentRoot("c:\\<content-root>")
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.
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
Ambiente
Define o ambiente do aplicativo.
Chave: ambiente
Tipo: string
Padrão: Produção
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 Usar vários ambientes no
ASP.NET Core.
WebHost.CreateDefaultBuilder(args)
.UseEnvironment(EnvironmentName.Development)
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "assembly1;assembly2")
Porta HTTPS
Defina a porta de redirecionamento HTTPS. Uso em aplicação de HTTPS.
Chave: https_port Tipo: cadeia de caracteres Padrão: um valor padrão não está definido. Definir usando:
UseSetting Variável de ambiente: ASPNETCORE_HTTPS_PORT
WebHost.CreateDefaultBuilder(args)
.UseSetting("https_port", "8080")
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, "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.
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 método de extensão UseShutdownTimeout usa um
TimeSpan.
Durante o período de tempo limite, a hospedagem:
Dispara IApplicationLifetime.ApplicationStopping.
Tenta parar os serviços hospedados, registrando em log os erros dos serviços que falham ao parar.
Se o período de tempo limite expirar antes que todos os serviços hospedados parem, os serviços ativos
restantes serão parados quando o aplicativo for desligado. Os serviços serão parados mesmo se ainda não
tiverem concluído o processamento. Se os serviços exigirem mais tempo para parar, aumente o tempo limite.
WebHost.CreateDefaultBuilder(args)
.UseShutdownTimeout(TimeSpan.FromSeconds(10))
Assembly de inicialização
Determina o assembly para pesquisar pela classe Startup .
Chave: startupAssembly
Tipo: string
Padrão: o assembly do aplicativo
Definido usando: UseStartup
Variável de ambiente: ASPNETCORE_STARTUPASSEMBLY
O assembly por nome ( string ) ou por tipo ( TStartup ) pode ser referenciado. Se vários métodos UseStartup
forem chamados, o último terá precedência.
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
WebHost.CreateDefaultBuilder(args)
.UseWebRoot("public")
Substituir configuração
Use Configuração para configurar o host Web. No exemplo a seguir, a configuração do host é especificada,
opcionalmente, em um arquivo hostsettings.json. Qualquer configuração carregada do arquivo hostsettings.json
pode ser substituída por argumentos de linha de comando. A configuração de build (no config ) é usada para
configurar o host com UseConfiguration. A configuração IWebHostBuilder é adicionada à configuração do
aplicativo, mas o contrário não é verdade— ConfigureAppConfiguration não afeta a configuração
IWebHostBuilder .
Substituição da configuração fornecida por UseUrls pela configuração de hostsettings.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!"));
});
}
}
hostsettings.json:
{
urls: "http://*:5005"
}
NOTE
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.
UseConfiguration somente copia as chaves do IConfiguration fornecido para a configuração do construtor de host.
Portanto, definir reloadOnChange: true para arquivos de configuração JSON, INI e XML não tem nenhum efeito.
Para especificar o host executado em uma URL específica, o valor desejado pode ser passado em um prompt
de comando ao executar dotnet run. O argumento de linha de comando substitui o valor urls do arquivo
hostsettings.json e o servidor escuta na porta 8080:
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<IRouteBuilder> 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 a
WaitForShutdown
mensagem Console.WriteLine e aguarda um pressionamento de tecla para ser encerrado.
Start(string url, Action<IRouteBuilder> 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 shut down the host...");
host.WaitForShutdown();
}
StartWith(Action<IApplicationBuilder> 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<IApplicationBuilder> 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 :
NOTE
Além do método de extensão IsDevelopment , IHostingEnvironment oferece os métodos IsStaging ,
IsProduction e IsEnvironment(string environmentName) . Para obter mais informações, consulte Usar vários
ambientes no ASP.NET Core.
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.
StopApplication solicita o término do aplicativo. A classe a seguir usa StopApplication para desligar
normalmente um aplicativo quando o método Shutdown da classe é chamado:
Validação de escopo
CreateDefaultBuilder define ServiceProviderOptions.ValidateScopes como true quando o ambiente do
aplicativo é de desenvolvimento.
Quando ValidateScopes está definido como true , o provedor de serviço padrão executa verificações para
saber se:
Os serviços com escopo não são resolvidos direta ou indiretamente pelo provedor de serviço raiz.
Os serviços com escopo não são injetados direta ou indiretamente em singletons.
O provedor de serviços raiz é criado quando BuildServiceProvider é chamado. O tempo de vida do provedor
de serviço raiz corresponde ao tempo de vida do aplicativo/servidor quando o provedor começa com o
aplicativo e é descartado quando o aplicativo é desligado.
Os serviços com escopo são descartados pelo contêiner que os criou. Se um serviço com escopo é criado no
contêiner raiz, o tempo de vida do serviço é promovido efetivamente para singleton, porque ele só é
descartado pelo contêiner raiz quando o aplicativo/servidor é desligado. A validação dos escopos de serviço
detecta essas situações quando BuildServiceProvider é chamado.
Para que os escopos sempre sejam validados, incluindo no ambiente de produção, configure
ServiceProviderOptions com UseDefaultServiceProvider no construtor do host:
WebHost.CreateDefaultBuilder(args)
.UseDefaultServiceProvider((context, options) => {
options.ValidateScopes = true;
})
Recursos adicionais
Hospedar o ASP.NET Core no Windows com o IIS
Host ASP.NET Core no Linux com Nginx
Hospedar o ASP.NET Core no Linux com o Apache
Hospedar o ASP.NET Core em um serviço Windows
Host Genérico .NET
12/12/2018 • 19 minutes to read • Edit Online
Introdução
A biblioteca do Host Genérico está disponível no namespace Microsoft.Extensions.Hosting e é fornecida pelo
pacote Microsoft.Extensions.Hosting. O pacote Microsoft.Extensions.Hosting está incluído no metapacote
Microsoft.AspNetCore.App (ASP.NET Core 2.1 ou posterior).
IHostedService é o ponto de entrada para a execução de código. Cada implementação do IHostedService é
executada na ordem do registro de serviço em ConfigureServices. StartAsync é chamado em cada
IHostedService quando o host é iniciado e StopAsync é chamado na ordem inversa de registro quando o host é
desligado normalmente.
Configurar um host
IHostBuilder é o principal componente usado por aplicativos e bibliotecas para inicializar, compilar e executar o
host:
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.Build();
await host.RunAsync();
}
Opções
Os HostOptions configuram opções para o IHost.
Tempo limite de desligamento
O ShutdownTimeout define o tempo limite para StopAsync. O valor padrão é cinco segundos.
A seguinte configuração de opção no Program.Main aumenta o tempo limite de desligamento padrão de cinco
segundos para 20 segundos:
Serviços padrão
Os seguintes serviços são registrados durante a inicialização do host:
Ambiente (IHostingEnvironment)
HostBuilderContext
Configuração (IConfiguration)
IApplicationLifetime (ApplicationLifetime)
IHostLifetime (ConsoleLifetime)
IHost
Opções (AddOptions)
Registro em log (AddLogging)
Configuração do host
A configuração do host é criada:
Chamando métodos de extensão em IHostBuilder para definir a raiz do conteúdo e o ambiente.
Lendo a configuração de provedores de configuração no ConfigureHostConfiguration.
Métodos de extensão
Chave do aplicativo (nome )
A propriedade IHostingEnvironment.ApplicationName é definida na configuração do host durante a construção
do host. Para definir o valor explicitamente, use o HostDefaults.ApplicationKey:
Chave: applicationName
Tipo: string
Padrão: o nome do assembly que contém o ponto de entrada do aplicativo.
Definido usando: HostBuilderContext.HostingEnvironment.ApplicationName
Variável de ambiente: <PREFIX_>APPLICATIONNAME ( <PREFIX_> é opcional e definida pelo usuário)
Raiz do conteúdo
Essa configuração determina onde o host começa a procurar por arquivos de conteúdo.
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: <PREFIX_>CONTENTROOT ( <PREFIX_> é opcional e definida pelo usuário)
Se o caminho não existir, o host não será iniciado.
Ambiente
Define o ambiente do aplicativo.
Chave: ambiente
Tipo: string
Padrão: Production
Definido usando: UseEnvironment
Variável de ambiente: <PREFIX_>ENVIRONMENT ( <PREFIX_> é opcional e definida pelo usuário)
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.
ConfigureHostConfiguration
ConfigureHostConfiguration usa um IConfigurationBuilder para criar um IConfiguration para o host. A
configuração do host é usada para inicializar o IHostingEnvironment para uso no processo de build do
aplicativo.
ConfigureHostConfiguration pode ser chamado várias vezes com resultados aditivos. O host usa a opção que
define um valor por último em uma chave determinada.
A configuração do host flui automaticamente para a configuração do aplicativo (ConfigureAppConfiguration e o
restante do aplicativo).
Não há provedores incluídos por padrão. Você deve especificar explicitamente os provedores de configuração
exigidos pelo aplicativo em ConfigureHostConfiguration , incluindo:
Configuração de arquivo (por exemplo, de um arquivo hostsettings.json).
Configuração de variável de ambiente.
Configuração de argumento de linha de comando.
Demais provedores de configuração necessários.
A configuração de arquivo do host é habilitada especificando o caminho base do aplicativo com SetBasePath
seguido por uma chamada a um dos provedores de configuração do arquivo. O aplicativo de exemplo usa um
arquivo JSON, hostsettings.json, e chamadas AddJsonFile para consumir as definições de configuração de host
do arquivo.
Para adicionar uma configuração de variável de ambiente do host, chame AddEnvironmentVariables no
construtor do host. AddEnvironmentVariables aceita um prefixo opcional definido pelo usuário. O aplicativo de
exemplo usa um prefixo igual a PREFIX_ . O prefixo é removido quando as variáveis de ambiente são lidas.
Quando o host do aplicativo de exemplo é configurado, o valor da variável de ambiente de PREFIX_ENVIRONMENT
torna-se o valor de configuração de host para a chave environment .
Durante o desenvolvimento, ao usar o Visual Studio ou executar um aplicativo com dotnet run , as variáveis de
ambiente poderão ser definidas no arquivo Properties/launchSettings.json. No Visual Studio Code, as variáveis
de ambiente podem ser definidas no arquivo .vscode/launch.json durante o desenvolvimento. Para obter mais
informações, consulte Usar vários ambientes no ASP.NET Core.
A configuração de linha de comando é adicionada chamando AddCommandLine. A configuração de linha de
comando é adicionada por último para permitir que os argumentos de linha de comando substituam a
configuração fornecida pelos provedores de configuração anteriores.
hostsettings.json:
{
"environment": "Development"
}
ConfigureAppConfiguration
A configuração de aplicativo é criada chamando ConfigureAppConfiguration na implementação IHostBuilder.
ConfigureAppConfiguration usa um IConfigurationBuilder para criar um IConfiguration para o aplicativo.
ConfigureAppConfiguration pode ser chamado várias vezes com resultados aditivos. O aplicativo usa a opção
que define um valor por último em uma chave determinada. A configuração criada pelo
ConfigureAppConfiguration está disponível em HostBuilderContext.Configuration para operações subsequentes
e em Services.
A configuração do aplicativo recebe automaticamente a configuração de host fornecida por
ConfigureHostConfiguration.
Exemplo da configuração de aplicativo usando ConfigureAppConfiguration :
var host = new HostBuilder()
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.SetBasePath(Directory.GetCurrentDirectory());
configApp.AddJsonFile("appsettings.json", optional: true);
configApp.AddJsonFile(
$"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json",
optional: true);
configApp.AddEnvironmentVariables(prefix: "PREFIX_");
configApp.AddCommandLine(args);
})
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
appsettings.Production.json:
{
"Logging": {
"LogLevel": {
"Default": "Error",
"System": "Information",
"Microsoft": "Information"
}
}
}
Para mover arquivos de configurações para o diretório de saída, especifique os arquivos de configurações como
itens de projeto do MSBuild no arquivo de projeto. O aplicativo de exemplo move os arquivos de configurações
de aplicativo JSON e hostsettings.json com o seguinte item <Content> :
<ItemGroup>
<Content Include="**\*.json" Exclude="bin\**\*;obj\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
ConfigureServices
ConfigureServices adiciona serviços ao contêiner injeção de dependência do aplicativo. ConfigureServices pode
ser chamado várias vezes com resultados aditivos.
Um serviço hospedado é uma classe com lógica de tarefa em segundo plano que implementa a interface
IHostedService. Para obter mais informações, consulte Tarefas em segundo plano com serviços hospedados no
ASP.NET Core.
O aplicativo de exemplo usa o método de extensão AddHostedService para adicionar um serviço para eventos
de tempo de vida, LifetimeEventsHostedService , e uma tarefa em segundo plano programada,
TimedHostedService , para o aplicativo:
services.AddHostedService<LifetimeEventsHostedService>();
services.AddHostedService<TimedHostedService>();
})
ConfigureLogging
ConfigureLogging adiciona um delegado para configurar o ILoggingBuilder fornecido. ConfigureLogging pode
ser chamado várias vezes com resultados aditivos.
UseConsoleLifetime
UseConsoleLifetime escuta Ctrl+C /SIGINT ou SIGTERM e chama StopApplication para iniciar o processo de
desligamento. UseConsoleLifetime desbloqueia extensões como RunAsync e WaitForShutdownAsync.
ConsoleLifetime é previamente registrado como a implementação de tempo de vida padrão. O último tempo de
vida registrado é usado.
Configuração do contêiner
Para dar suporte à conexão de outros contêineres, o host pode aceitar um
IServiceProviderFactory<TContainerBuilder>. O fornecimento de um alocador não faz parte do registro de
contêiner de injeção de dependência, mas, em vez disso, um host intrínseco é usado para criar o contêiner de DI
concreto. UseServiceProviderFactory(IServiceProviderFactory<TContainerBuilder>) substitui o alocador padrão
usado para criar o provedor de serviços do aplicativo.
A configuração de contêiner personalizado é gerenciada pelo método ConfigureContainer. ConfigureContainer
fornece uma experiência fortemente tipada para configurar o contêiner sobre a API do host subjacente.
ConfigureContainer pode ser chamado várias vezes com resultados aditivos.
namespace GenericHostSample
{
internal class ServiceContainer
{
}
}
using System;
using Microsoft.Extensions.DependencyInjection;
namespace GenericHostSample
{
internal class ServiceContainerFactory : IServiceProviderFactory<ServiceContainer>
{
public ServiceContainer CreateBuilder(IServiceCollection services)
{
return new ServiceContainer();
}
Extensibilidade
Extensibilidade de host é executada com métodos de extensão em IHostBuilder . O exemplo a seguir mostra
como um método de extensão estende uma implementação do IHostBuilder com o exemplo do
TimedHostedService, demonstrado no Tarefas em segundo plano com serviços hospedados no ASP.NET Core.
await host.StartAsync();
Um aplicativo estabelece o método de extensão UseHostedService para registrar o serviço hospedado passado
no T :
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Gerenciar o host
A implementação IHost é responsável por iniciar e parar as implementações IHostedService que estão
registradas no contêiner de serviço.
Executar
Run executa o aplicativo e bloqueia o thread de chamada até que o host seja desligado:
host.Run();
}
}
RunAsync
RunAsync executa o aplicativo e retorna um Task que é concluído quando o token de cancelamento ou o
desligamento é disparado:
await host.RunAsync();
}
}
RunConsoleAsync
RunConsoleAsync habilita o suporte do console, compila e inicia o host e aguarda Ctrl+C /SIGINT ou
SIGTERM desligar.
public class Program
{
public static async Task Main(string[] args)
{
var hostBuilder = new HostBuilder();
await hostBuilder.RunConsoleAsync();
}
}
Start e StopAsync
Start inicia o host de forma síncrona.
StopAsync tenta parar o host dentro do tempo limite fornecido.
using (host)
{
host.Start();
await host.StopAsync(TimeSpan.FromSeconds(5));
}
}
}
StartAsync e StopAsync
StartAsync inicia o aplicativo.
StopAsync interrompe o aplicativo.
using (host)
{
await host.StartAsync();
await host.StopAsync();
}
}
}
WaitForShutdown
WaitForShutdown é disparado por meio de IHostLifetime, como ConsoleLifetime (escuta Ctrl+C /SIGINT ou
SIGTERM ). WaitForShutdown chama StopAsync.
public class Program
{
public void Main(string[] args)
{
var host = new HostBuilder()
.Build();
using (host)
{
host.Start();
host.WaitForShutdown();
}
}
}
WaitForShutdownAsync
WaitForShutdownAsync retorna um Task que é concluído quando o desligamento é disparado por meio do
token fornecido e chama StopAsync.
using (host)
{
await host.StartAsync();
await host.WaitForShutdownAsync();
}
}
}
Controle externo
O controle externo do host pode ser obtido usando os métodos que podem ser chamados externamente:
public class Program
{
private IHost _host;
public Program()
{
_host = new HostBuilder()
.Build();
}
WaitForStartAsync é chamado no início de StartAsync, que aguarda até que ele seja concluído antes de
continuar. Isso pode ser usado para atrasar a inicialização até que seja sinalizado por um evento externo.
Interface IHostingEnvironment
IHostingEnvironment fornece informações sobre o ambiente de hospedagem do aplicativo. Use a injeção de
construtor para obter o IHostingEnvironment para usar suas propriedades e métodos de extensão:
Para obter mais informações, consulte Usar vários ambientes no ASP.NET Core.
Interface IApplicationLifetime
IApplicationLifetime permite atividades pós-inicialização e desligamento, inclusive solicitações de desligamento
normal. Três propriedades na interface são tokens de cancelamento usados para registrar métodos Action que
definem eventos de inicialização e desligamento.
O construtor injeta o serviço IApplicationLifetime em qualquer classe. O aplicativo de exemplo usa injeção de
construtor em uma classe LifetimeEventsHostedService (uma implementação IHostedService ) para registrar os
eventos.
LifetimeEventsHostedService.cs:
internal class LifetimeEventsHostedService : IHostedService
{
private readonly ILogger _logger;
private readonly IApplicationLifetime _appLifetime;
public LifetimeEventsHostedService(
ILogger<LifetimeEventsHostedService> logger, IApplicationLifetime appLifetime)
{
_logger = logger;
_appLifetime = appLifetime;
}
return Task.CompletedTask;
}
StopApplication solicita o término do aplicativo. A classe a seguir usa StopApplication para desligar
normalmente um aplicativo quando o método Shutdown da classe é chamado:
public class MyClass
{
private readonly IApplicationLifetime _appLifetime;
Recursos adicionais
Tarefas em segundo plano com serviços hospedados no ASP.NET Core
Hospedagem de exemplos do repositório no GitHub
Implementações de servidor Web em ASP.NET Core
21/01/2019 • 21 minutes to read • Edit Online
Modelos de hospedagem
Modelo de hospedagem em processo
Usando uma hospedagem em processo, um aplicativo ASP.NET Core é executado no mesmo processo que seu
processo de trabalho do IIS. A hospedagem em processo oferece desempenho melhor em hospedagem fora do
processo porque as solicitações não são transmitidas por proxy pelo adaptador de loopback, um adaptador de
rede que retorna o tráfego de rede de saída para o mesmo computador. O IIS manipula o gerenciamento de
processos com o WAS (Serviço de Ativação de Processos do Windows).
O Módulo do ASP.NET Core:
Executa a inicialização do aplicativo.
Carrega o CoreCLR.
Chama Program.Main .
Manipula o tempo de vida da solicitação nativa do IIS.
Não há suporte para o modelo de hospedagem em processo para aplicativos ASP.NET Core direcionados ao
.NET Framework.
O diagrama a seguir ilustra a relação entre o IIS, o Módulo do ASP.NET Core e um aplicativo hospedado em
processo:
A solicitação chega da Web para o driver do HTTP.sys no modo kernel. O driver roteia as solicitações nativas ao
IIS na porta configurada do site, normalmente, a 80 (HTTP ) ou a 443 (HTTPS ). O módulo recebe a solicitação
nativa e a passa para o Servidor HTTP do IIS ( IISHttpServer ). O servidor HTTP do IIS é uma implementação de
servidor em processo do IIS que converte a solicitação de nativa para gerenciada.
Depois que o Servidor HTTP do IIS processa a solicitação, a solicitação é enviada por push para o pipeline de
middleware do ASP.NET Core. O pipeline do middleware manipula a solicitação e a passa como uma instância
de HttpContext para a lógica do aplicativo. A resposta do aplicativo é retornada ao IIS, que a retorna por push
para o cliente que iniciou a solicitação.
A hospedagem em processo é uma opção de aceitação para os aplicativos existentes, mas o padrão dos modelos
dotnet new é o modelo de hospedagem em processo para todos os cenários do IIS e do IIS Express.
Modelo de hospedagem de fora do processo
Como os aplicativos ASP.NET Core são executados em um processo separado do processo de trabalho do IIS, o
módulo realiza o gerenciamento de processos. O módulo inicia o processo para o aplicativo ASP.NET Core
quando a primeira solicitação chega e reinicia o aplicativo se ele é desligado ou falha. Isso é basicamente o
mesmo comportamento que o dos aplicativos que são executados dentro do processo e são gerenciados pelo
WAS (Serviço de Ativação de Processos do Windows).
O diagrama a seguir ilustra a relação entre o IIS, o Módulo do ASP.NET Core e um aplicativo hospedado de fora
d processo:
As solicitações chegam da Web para o driver do HTTP.sys no modo kernel. O driver roteia as solicitações ao IIS
na porta configurada do site, normalmente, a 80 (HTTP ) ou a 443 (HTTPS ). O módulo encaminha as solicitações
ao Kestrel em uma porta aleatória do aplicativo, que não seja a porta 80 ou 443.
O módulo especifica a porta por meio de uma variável de ambiente na inicialização e o middleware de
integração do IIS configura o servidor para escutar em http://localhost:{PORT} . Outras verificações são
executadas e as solicitações que não se originam do módulo são rejeitadas. O módulo não é compatível com
encaminhamento de HTTPS, portanto, as solicitações são encaminhadas por HTTP, mesmo se recebidas pelo IIS
por HTTPS.
Depois que o Kestrel coleta a solicitação do módulo, a solicitação é enviada por push ao pipeline do middleware
do ASP.NET Core. O pipeline do middleware manipula a solicitação e a passa como uma instância de
HttpContext para a lógica do aplicativo. O middleware adicionado pela integração do IIS atualiza o esquema, o
IP remoto e pathbase para encaminhar a solicitação para o Kestrel. A resposta do aplicativo é retornada ao IIS,
que a retorna por push para o cliente HTTP que iniciou a solicitação.
Para obter as diretrizes de configuração do IIS e do módulo do ASP.NET Core, confira os tópicos a seguir:
Hospedar o ASP.NET Core no Windows com o IIS
Módulo do ASP.NET Core
Windows
macOS
Linux
O ASP.NET Core vem com os seguintes itens:
O servidor Kestrel é o servidor HTTP padrão multiplataforma.
O servidor HTTP.sys é um servidor HTTP somente do Windows com base no driver do kernel HTTP.sys e na
API do servidor HTTP.
Ao usar o IIS ou o IIS Express, o aplicativo é executado em um processo separado do processo de trabalho do
IIS (fora do processo) com o servidor Kestrel.
Como os aplicativos ASP.NET Core são executados em um processo separado do processo de trabalho do IIS, o
módulo realiza o gerenciamento de processos. O módulo inicia o processo para o aplicativo ASP.NET Core
quando a primeira solicitação chega e reinicia o aplicativo se ele é desligado ou falha. Isso é basicamente o
mesmo comportamento que o dos aplicativos que são executados dentro do processo e são gerenciados pelo
WAS (Serviço de Ativação de Processos do Windows).
O diagrama a seguir ilustra a relação entre o IIS, o Módulo do ASP.NET Core e um aplicativo hospedado de fora
d processo:
As solicitações chegam da Web para o driver do HTTP.sys no modo kernel. O driver roteia as solicitações ao IIS
na porta configurada do site, normalmente, a 80 (HTTP ) ou a 443 (HTTPS ). O módulo encaminha as solicitações
ao Kestrel em uma porta aleatória do aplicativo, que não seja a porta 80 ou 443.
O módulo especifica a porta por meio de uma variável de ambiente na inicialização e o middleware de
integração do IIS configura o servidor para escutar em http://localhost:{port} . Outras verificações são
executadas e as solicitações que não se originam do módulo são rejeitadas. O módulo não é compatível com
encaminhamento de HTTPS, portanto, as solicitações são encaminhadas por HTTP, mesmo se recebidas pelo IIS
por HTTPS.
Depois que o Kestrel coleta a solicitação do módulo, a solicitação é enviada por push ao pipeline do middleware
do ASP.NET Core. O pipeline do middleware manipula a solicitação e a passa como uma instância de
HttpContext para a lógica do aplicativo. O middleware adicionado pela integração do IIS atualiza o esquema, o
IP remoto e pathbase para encaminhar a solicitação para o Kestrel. A resposta do aplicativo é retornada ao IIS,
que a retorna por push para o cliente HTTP que iniciou a solicitação.
Para obter as diretrizes de configuração do IIS e do módulo do ASP.NET Core, confira os tópicos a seguir:
Hospedar o ASP.NET Core no Windows com o IIS
Módulo do ASP.NET Core
Kestrel
O Kestrel é o servidor Web padrão incluído nos modelos de projeto do ASP.NET Core.
O Kestrel pode ser usado:
Sozinho, como um servidor de borda que processa solicitações diretamente de uma rede, incluindo a
Internet.
Com um servidor proxy reverso como IIS (Serviços de Informações da Internet), Nginx ou Apache. Um
servidor proxy reverso recebe solicitações HTTP da Internet e encaminha-as para o Kestrel.
Qualquer configuração de hospedagem (com ou sem um servidor proxy reverso) é compatível com os
aplicativos ASP.NET Core 2.1 ou posteriores.
Se o aplicativo aceita somente solicitações de uma rede interna, o Kestrel pode ser usado sozinho.
Se o aplicativo for exposto à Internet, o Kestrel deverá usar um servidor proxy reverso, como o IIS (Serviços de
Informações da Internet), Nginx ou Apache. Um servidor proxy reverso recebe solicitações HTTP da Internet e
encaminha-as para o Kestrel.
O motivo mais importante para usar um proxy reverso para implantações de servidores de borda voltados para
o público que são expostas diretamente na Internet é a segurança. As versões 1.x do Kestrel não incluem
recursos de segurança importantes para proteção contra ataques da Internet. Isso inclui, mas não se limita aos
tempos limite, limites de tamanho da solicitação e limites de conexões simultâneas apropriados.
Para obter diretrizes de configuração do Kestrel e informações sobre quando usar o Kestrel em uma
configuração de proxy reverso, confira Implementação do servidor Web Kestrel no ASP.NET Core.
Nginx com Kestrel
Para obter informações sobre como usar Nginx no Linux como um servidor proxy reverso para Kestrel, confira
Host ASP.NET Core no Linux com Nginx.
Apache com Kestrel
Para obter informações sobre como usar Apache no Linux como um servidor proxy reverso para Kestrel, confira
Hospedar o ASP.NET Core no Linux com o Apache.
HTTP.sys
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 as funcionalidades necessárias são compatíveis com HTTP.sys, mas não com
Kestrel. Para obter mais informações, consulte Implementação do servidor Web HTTP.sys no ASP.NET Core.
O HTTP.sys também pode ser usado para aplicativos que são expostos somente a uma rede interna.
Para obter as diretrizes de configuração do HTTP.sys, confira Implementação do servidor Web HTTP.sys no
ASP.NET Core.
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
O servidor é iniciado quando o IDE (Ambiente de Desenvolvimento Integrado) ou o editor inicia o aplicativo:
Visual Studio – 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.
Visual Studio Code – o aplicativo e o servidor são iniciados pelo Omnisharp, que ativa o depurador CoreCLR.
Visual Studio para Mac – o aplicativo e o servidor são iniciados pelo Depurador de modo suave Mono.
Ao iniciar o aplicativo usando um prompt de comando na pasta do projeto, o dotnet run inicia o aplicativo e o
servidor (apenas Kestrel e 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, confira dotnet run e pacote de distribuição do .NET
Core.
Recursos adicionais
Implementação do servidor Web Kestrel no ASP.NET Core
Módulo do ASP.NET Core
Hospedar o ASP.NET Core no Windows com o IIS
Implantar aplicativos ASP.NET Core no Serviço de Aplicativo do Azure
Host ASP.NET Core no Linux com Nginx
Hospedar o ASP.NET Core no Linux com o Apache
Implementação do servidor Web HTTP.sys no ASP.NET Core
Iniciar solicitações HTTP
30/01/2019 • 24 minutes to read • Edit Online
Pré-requisitos
Os projetos direcionados ao .NET Framework exigem a instalação do pacote do NuGet Microsoft.Extensions.Http.
Os projetos destinados ao .Net Core e a referência ao metapacote Microsoft.AspNetCore.App já incluem o pacote
Microsoft.Extensions.Http .
Padrões de consumo
Há várias maneiras de usar o IHttpClientFactory em um aplicativo:
Uso básico
Clientes nomeados
Clientes com tipo
Clientes gerados
Nenhum deles é estritamente superiores ao outro. A melhor abordagem depende das restrições do aplicativo.
Uso básico
O IHttpClientFactory pode ser registrado chamando o método de extensão AddHttpClient em
IServiceCollection , dentro do método Startup.ConfigureServices .
services.AddHttpClient();
Depois de registrado, o código pode aceitar um IHttpClientFactory em qualquer lugar em que os serviços
possam ser injetados com injeção de dependência. O IHttpClientFactory pode ser usado para criar uma instância
de HttpClient :
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
if (response.IsSuccessStatusCode)
{
Branches = await response.Content
.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Usar o IHttpClientFactory dessa forma é uma ótima maneira de refatorar um aplicativo existente. Não há
nenhum impacto na maneira em que o HttpClient é usado. Nos locais em que as instâncias de HttpClient são
criadas no momento, substitua essas ocorrências por uma chamada a CreateClient.
Clientes nomeados
Quando um aplicativo exige vários usos distintos do HttpClient , cada um com uma configuração diferente, uma
opção é usar clientes nomeados. A configuração de um HttpClient nomeado pode ser especificada durante o
registro em Startup.ConfigureServices .
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
No código anterior, AddHttpClient é chamado, fornecendo o nome github. Esse cliente tem algumas
configurações padrão aplicadas, ou seja, o endereço básico e dois cabeçalhos necessários para trabalhar com a
API do GitHub.
Toda vez que o CreateClient é chamado, uma nova instância de HttpClient é criada e a ação de configuração é
chamada.
Para consumir um cliente nomeado, um parâmetro de cadeia de caracteres pode ser passado para CreateClient .
Especifique o nome do cliente a ser criado:
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content
.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
No código anterior, a solicitação não precisa especificar um nome do host. Ela pode passar apenas o caminho, pois
o endereço básico configurado para o cliente é usado.
Clientes com tipo
Os clientes com tipo fornecem as mesmas funcionalidade que os clientes nomeados sem a necessidade de usar
cadeias de caracteres como chaves. A abordagem de cliente com tipo fornece a ajuda do IntelliSense e do
compilador durante o consumo de clientes. Eles fornecem um único local para configurar e interagir com um
determinado HttpClient . Por exemplo, um único cliente com tipo pode ser usado para um único ponto de
extremidade de back-end e encapsular toda a lógica que lida com esse ponto de extremidade. Outra vantagem é
que eles funcionam com a DI e podem ser injetados no local necessário no aplicativo.
Um cliente com tipo aceita um parâmetro HttpClient em seu construtor:
public class GitHubService
{
public HttpClient Client { get; }
Client = client;
}
response.EnsureSuccessStatusCode();
return result;
}
}
No código anterior, a configuração é movida para o cliente com tipo. O objeto HttpClient é exposto como uma
propriedade pública. É possível definir métodos específicos da API que expõem a funcionalidade HttpClient . O
método GetAspNetDocsIssues encapsula o código necessário para consultar e analisar os últimos problemas em
aberto de um repositório GitHub.
Para registrar um cliente com tipo, o método de extensão AddHttpClient genérico pode ser usado em
Startup.ConfigureServices , especificando a classe do cliente com tipo:
services.AddHttpClient<GitHubService>();
O cliente com tipo é registrado como transitório com a DI. O cliente com tipo pode ser injetado e consumido
diretamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
Se preferir, a configuração de um cliente com tipo poderá ser especificada durante o registro em
Startup.ConfigureServices e não no construtor do cliente com tipo:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
É possível encapsular totalmente o HttpClient dentro de um cliente com tipo. Em vez de o expor como uma
propriedade, é possível fornecer métodos públicos que chamam a instância de HttpClient internamente.
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
response.EnsureSuccessStatusCode();
return result;
}
}
No código anterior, o HttpClient é armazenado como um campo privado. Todo o acesso para fazer chamadas
externas passa pelo método GetRepos .
Clientes gerados
O IHttpClientFactory pode ser usado em combinação com outras bibliotecas de terceiros, como a Refit. Refit é
uma biblioteca REST para .NET. Ela converte APIs REST em interfaces dinâmicas. Uma implementação da
interface é gerada dinamicamente pelo RestService usando HttpClient para fazer as chamadas de HTTP
externas.
Uma interface e uma resposta são definidas para representar a API externa e sua resposta:
Um cliente com tipo pode ser adicionado usando a Refit para gerar a implementação:
services.AddMvc();
}
A interface definida pode ser consumida, quando necessário, com a implementação fornecida pela DI e pela Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
O código anterior define um manipulador básico. Ele verifica se um cabeçalho X-API-KEY foi incluído na
solicitação. Se o cabeçalho estiver ausente, isso poderá evitar a chamada de HTTP e retornar uma resposta
adequada.
Durante o registro, um ou mais manipuladores podem ser adicionados à configuração de um HttpClient . Essa
tarefa é realizada por meio de métodos de extensão no IHttpClientBuilder.
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Use uma das seguintes abordagens para compartilhar o estado por solicitação com os manipuladores de
mensagens:
Passe os dados pelo manipulador usando HttpRequestMessage.Properties .
Use IHttpContextAccessor para acessar a solicitação atual.
Crie um objeto de armazenamento AsyncLocal personalizado para passar os dados.
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="2.1.1" />
</ItemGroup>
</Project>
Depois de restaurar este pacote, os métodos de extensão ficam disponíveis para permitir a adição de
manipuladores baseados no Polly aos clientes.
Tratar falhas transitórias
As falhas mais comuns ocorrem quando as chamadas HTTP externas são transitórias. Um método de extensão
conveniente chamado AddTransientHttpErrorPolicy é incluído para permitir que uma política seja definido para
tratar erros transitórios. As políticas configuradas com esse método de extensão tratam HttpRequestException ,
respostas HTTP 5xx e respostas HTTP 408.
A extensão AddTransientHttpErrorPolicy pode ser usada em Startup.ConfigureServices . A extensão fornece
acesso a um objeto PolicyBuilder configurado para tratar erros que representam uma possível falha transitória:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
No código anterior, uma política WaitAndRetryAsync é definida. As solicitações com falha são repetidas até três
vezes com um atraso de 600 ms entre as tentativas.
Selecionar políticas dinamicamente
Existem métodos de extensão adicionais que podem ser usados para adicionar manipuladores baseados no Polly.
Uma dessas extensões é a AddPolicyHandler , que tem várias sobrecargas. Uma sobrecarga permite que a
solicitação seja inspecionada durante a definição da política a ser aplicada:
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
No código anterior, se a solicitação de saída é um GET, é aplicado um tempo limite de 10 segundos. Para qualquer
outro método HTTP, é usado um tempo limite de 30 segundos.
Adicionar vários manipuladores do Polly
É comum aninhar políticas do Polly para fornecer uma funcionalidade avançada:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
No código anterior, duas políticas são registradas quando PolicyRegistry é adicionado ao ServiceCollection .
Para usar uma política do registro, o método AddPolicyHandlerFromRegistry é usado, passando o nome da política
a ser aplicada.
Mais informações sobre o IHttpClientFactory e as integrações do Polly podem ser encontradas no Wiki do Polly.
IHttpClientFactory cria pools com as instâncias de HttpMessageHandler criadas pelo alocador para reduzir o
consumo de recursos. Uma instância de HttpMessageHandler poderá ser reutilizada no pool, ao criar uma nova
instância de HttpClient , se o respectivo tempo de vida não tiver expirado.
O pooling de manipuladores é preferível porque normalmente cada manipulador gerencia as próprias conexões
HTTP subjacentes. Criar mais manipuladores do que o necessário pode resultar em atrasos de conexão. Alguns
manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador
reaja a alterações de DNS.
O tempo de vida padrão do manipulador é de 2 minutos. O valor padrão pode ser substituído para cada cliente
nomeado. Para substituí-lo, chame SetHandlerLifetime no IHttpClientBuilder que é retornado ao criar o cliente:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Não é necessário descartar o cliente. O descarte cancela as solicitações de saída e garante que a determinada
instância de HttpClient não seja usada depois de chamar Dispose. IHttpClientFactory rastreia e descarta
recursos usados pelas instâncias de HttpClient . Geralmente, as instâncias de HttpClient podem ser tratadas
como objetos do .NET e não exigem descarte.
Manter uma única instância de HttpClient ativa por uma longa duração é um padrão comum usado, antes do
início de IHttpClientFactory . Esse padrão se torna desnecessário após a migração para IHttpClientFactory .
Registrando em log
Os clientes criado pelo IHttpClientFactory registram mensagens de log para todas as solicitações. Habilite o nível
apropriado de informações na configuração de log para ver as mensagens de log padrão. Os registros em log
adicionais, como o registro em log dos cabeçalhos de solicitação, estão incluídos somente no nível de
rastreamento.
A categoria de log usada para cada cliente inclui o nome do cliente. Um cliente chamado MyNamedClient, por
exemplo, registra mensagens com uma categoria de System.Net.Http.HttpClient.MyNamedClient.LogicalHandler .
Mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na
solicitação, as mensagens são registradas em log antes que qualquer outro manipulador no pipeline a tenha
processado. Na resposta, as mensagens são registradas depois que qualquer outro manipulador do pipeline tenha
recebido a resposta.
O registro em log também ocorre dentro do pipeline do manipulador de solicitações. No caso do exemplo de
MyNamedClient, essas mensagens são registradas em log na categoria de log
System.Net.Http.HttpClient.MyNamedClient.ClientHandler . Para a solicitação, isso ocorre depois que todos os
outros manipuladores são executados e imediatamente antes que a solicitação seja enviada pela rede. Na
resposta, esse registro em log inclui o estado da resposta antes que ela seja retornada por meio do pipeline do
manipulador.
Habilitar o registro em log dentro e fora do pipeline permite a inspeção das alterações feitas por outros
manipuladores do pipeline. Isso pode incluir, por exemplo, alterações nos cabeçalhos de solicitação ou no código
de status da resposta.
Incluir o nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos,
quando necessário.
Configurar o HttpMessageHandler
Talvez seja necessário controlar a configuração do HttpMessageHandler interno usado por um cliente.
Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou com tipo. O método de extensão
ConfigurePrimaryHttpMessageHandler pode ser usado para definir um representante. O representante que é
usado para criar e configurar o HttpMessageHandler primário usado pelo cliente:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Introdução a Páginas do Razor no ASP.NET Core
26/12/2018 • 34 minutes to read • Edit Online
Pré-requisitos
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento
para a Web
SDK 2.2 ou posterior do .NET Core
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 model binding, 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.
WARNING
Por motivos de segurança, você deve aceitar 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 a associação de GET é útil
ao lidar com cenários 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 ).
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
[Required(ErrorMessage = "Color is required")]
public string Color { get; set; }
// Process color.
return RedirectToPage("./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. No ASP.NET Core 2.1 e 2.2, esse
comportamento ocorre com o SetCompatibilityVersion em Startup.Configure :
services.AddMvc()
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
Em vez de aceitar todos os 2.1 comportamentos com SetCompatibilityVersion , você pode explicitamente
participar de comportamentos específicos. O código a seguir aceita as solicitações de mapeamento HEAD
para o manipulador GET.
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";
}
O layout está na pasta Pages/Shared. 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/Shared pode
ser usado em qualquer página do Razor na pasta Pages.
O arquivo de layout deve entrar na pasta Pages/Shared.
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, a classe PageModel 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 o Razor Pages Pages/Customers/Edit.cshtml é o mesmo que a classe PageModel .
@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 aplicativo tem a estrutura de arquivos/pastas a seguir:
/Pages
Index.cshtml
/Clientes
Create.cshtml
Edit.cshtml
Index.cshtml
As páginas Pages/Customers/Create.cshtml e Pages/Customers/Edit.cshtml redirecionam para o
Pages/Index.cshtml após êxito. A cadeia de caracteres /Index faz parte do URI para acessar a página
anterior. A cadeia de caracteres /Index pode ser usada para gerar URIs para a página Pages/Index.cshtml.
Por exemplo:
Url.Page("/Index", ...)
<a asp-page="/Index">My Index Page</a>
RedirectToPage("/Index")
O nome da página é o caminho para a página da pasta raiz /Pages, incluindo um / à direita (por exemplo,
/Index ). Os exemplos anteriores de geração de URL oferecem opções avançadas e recursos funcionais
para 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
Atributo ViewData
Os dados podem ser passados para uma página com ViewDataAttribute. As propriedades nos
controladores ou nos modelos da Página Razor decoradas com [ViewData] têm seus valores armazenados
e carregados em ViewDataDictionary.
No exemplo a seguir, o AboutModel contém uma propriedade Title decorada com [ViewData] .A
propriedade Title está definida como o título da página Sobre:
public class AboutModel : PageModel
{
[ViewData]
public string Title { get; } = "About";
<h1>@Model.Title</h1>
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
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 .
Rotas personalizadas
Use a diretiva @page para:
Especifique uma rota personalizada para uma página. Por exemplo, a rota para a página Sobre pode ser
definida como /Some/Other/Path com @page "/Some/Other/Path" .
Acrescente segmentos à rota padrão de uma página. Por exemplo, um segmento de "item" pode ser
adicionado à rota padrão da página com @page "item" .
Acrescente parâmetros à rota padrão de uma página. Por exemplo, um parâmetro de ID, id , pode ser
necessário para uma página com @page "{id}" .
Há suporte para um caminho relativo à raiz designado por um til ( ~ ) no início do caminho. Por exemplo,
@page "~/Some/Other/Path" é o mesmo que @page "/Some/Other/Path" .
Você pode alterar a cadeia de caracteres de consulta ?handler=JoinList na URL para um segmento de rota
/JoinList ao especificar o modelo de rota @page "{handler?}" .
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>
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");
Recursos adicionais
Introdução ao ASP.NET Core
Referência da sintaxe Razor para ASP.NET Core
Tutorial: Introdução às Páginas do Razor no ASP.NET Core
Convenções de autorização de páginas do Razor no ASP.NET Core
Convenções de rota e aplicativo das Páginas do Razor no ASP.NET Core
Testes de unidade de páginas do Razor no ASP.NET Core
Exibições parciais no ASP.NET Core
Tutorial: Criar um aplicativo Web de Páginas do
Razor com o ASP.NET Core
04/02/2019 • 2 minutes to read • Edit Online
Esta série de tutoriais explica as noções básicas sobre a criação de um aplicativo Web Razor Pages.
Para obter uma introdução mais avançada direcionada a desenvolvedores experientes, confira Introdução a Razor
Pages.
Esta série inclui os seguintes tutoriais:
1. Criar um aplicativo Web de Páginas do Razor
2. Adicionar um modelo a um aplicativo de Páginas do Razor
3. Gerar páginas do Razor por scaffolding
4. Trabalhar com um banco de dados
5. Atualizar páginas do Razor
6. Adicionar pesquisa
7. Adicionar um novo campo
8. Adicionar validação
No final, você terá um aplicativo que pode exibir e gerenciar um banco de dados de filmes.
Tutorial: Introdução às Páginas do Razor no
ASP.NET Core
04/02/2019 • 8 minutes to read • Edit Online
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para
a Web
SDK 2.2 ou posterior do .NET Core
Criar um aplicativo Web das Páginas do Razor
Visual Studio
Visual Studio Code
Visual Studio para Mac
No menu Arquivo do Visual Studio, selecione Novo > Projeto.
Crie um novo Aplicativo Web ASP.NET Core. Nomeie o projeto RazorPagesMovie. É importante
nomear o projeto RazorPagesMovie de modo que os namespaces façam a correspondência quando
você copiar e colar o código.
Selecione ASP.NET Core 2.2 na lista suspensa e, em seguida, selecione Aplicativo Web.
Próximas etapas
Neste tutorial, você:
Criou um aplicativo Web do Razor Pages.
Executou o aplicativo.
Examinou os arquivos de projeto.
Vá para o próximo tutorial da série:
A D IC IO N A R U M
M ODELO
Adicionar um modelo a um aplicativo Páginas Razor
no ASP.NET Core
28/01/2019 • 21 minutes 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; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Na caixa de diálogo Adicionar Scaffold, selecione Razor Pages usando o Entity Framework (CRUD ) >
Adicionar.
Conclua a caixa de diálogo Adicionar Razor Pages usando o Entity Framework (CRUD ):
Na lista suspensa Classe de modelo, selecione Filme (RazorPagesMovie.Models).
Na linha Classe de contexto de dados, selecione o sinal + (+) e aceite o nome gerado
RazorPagesMovie.Models.RazorPagesMovieContext.
Selecione Adicionar.
O arquivo appsettings.json é atualizado com a cadeia de conexão usada para se conectar a um banco de dados
local.
Os comandos anteriores geram o seguinte aviso: “Nenhum tipo foi especificado para a coluna decimal "Preço" no
tipo de entidade "Filme". Isso fará com que valores sejam truncados silenciosamente se não couberem na precisão
e na escala padrão. Especifique explicitamente o tipo de coluna do SQL Server que pode acomodar todos os
valores usando 'HasColumnType()'.”
Você pode ignorar esse aviso, ele será corrigido em um tutorial posterior.
O processo de scaffold cria e atualiza os arquivos a seguir:
Arquivos criados
Pages/Movies: Criar, Excluir, Detalhes, Editar e Índice.
Data/RazorPagesMovieContext.cs
Arquivo atualizado
Startup.cs
Os arquivos criados e atualizados são explicados na próxima seção.
Migração inicial
Visual Studio
Visual Studio Code
Visual Studio para Mac
Nesta seção, o PMC (Console de Gerenciador de Pacotes) é usado para:
Adicione uma migração inicial.
Atualize o banco de dados com a migração inicial.
No menu Ferramentas, selecione Gerenciador de pacotes NuGet > Console do Gerenciador de pacotes.
Add-Migration Initial
Update-Database
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 RazorPagesMovieContext.cs). O
argumento InitialCreate é usado para nomear as migrações. Qualquer nome pode ser usado, mas, por
convenção, um nome que descreve a migração é selecionado.
O comando ef database update executa o método Up no arquivo Migrations/<time-stamp>_InitialCreate.cs. O
método Up cria o banco de dados.
Visual Studio
Visual Studio Code
Visual Studio para Mac
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("RazorPagesMovieContext")));
}
O RazorPagesMovieContext coordena a funcionalidade do EF Core (Criar, Ler, Atualizar, Excluir etc.) para o modelo
Movie . O contexto de dados ( RazorPagesMovieContext ) deriva de Microsoft.EntityFrameworkCore.DbContext. O
contexto de dados especifica quais entidades são incluídas no modelo de dados.
using Microsoft.EntityFrameworkCore;
namespace RazorPagesMovie.Models
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext (DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}
O código anterior cria uma propriedade DbSet<Movie> para o conjunto de entidades. Na terminologia do Entity
Framework, um conjunto de entidades normalmente corresponde a uma tabela de banco de dados. Uma entidade
corresponde a uma linha da tabela.
O nome da cadeia de conexão é passado para o contexto com a chamada de um método em um objeto
DbContextOptions. Para o desenvolvimento local, o sistema de configuração do ASP.NET Core lê a cadeia de
conexão do arquivo appsettings.json.
O comando Add-Migration gera código para criar o esquema de banco de dados inicial. O esquema é baseado no
modelo especificado no RazorPagesMovieContext (no arquivo Data/RazorPagesMovieContext.cs). O argumento
Initial é usado para nomear as migrações. Qualquer nome pode ser usado, mas, por convenção, um nome que
descreve a migração é usado. Consulte Introdução às migrações para obter mais informações.
O comando Update-Database executa o método Up no arquivo Migrations/{time-stamp }_InitialCreate.cs, que cria
o banco de dados.
Testar o aplicativo
Executar o aplicativo e acrescentar /Movies à URL no navegador ( http://localhost:port/movies ).
SqlException: Cannot open database "RazorPagesMovieContext-GUID" requested by the login. The login failed.
Login failed for user 'User-name'.
NOTE
Talvez você não consiga inserir casas decimais ou vírgulas no campo Price . Para dar suporte à validação do jQuery
para localidades com idiomas diferentes do inglês que usam uma vírgula (",") para um ponto decimal e formatos de
data diferentes do inglês dos EUA, o aplicativo precisa ser globalizado. Para obter instruções sobre a globalização,
consulte esse problema no GitHub.
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.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Models.RazorPagesMovieContext _context;
As Páginas do Razor são derivadas de PageModel . Por convenção, a classe derivada de PageModel é chamada de
<PageName>Model . O construtor usa injeção de dependência para adicionar o RazorPagesMovieContext à página.
Todas as páginas geradas por scaffolding seguem esse padrão. Consulte Código assíncrono para obter mais
informações sobre a programação assíncrona com o Entity Framework.
Quando uma solicitação é feita à página, o método OnGetAsync retorna uma lista de filmes para a Página do
Razor. OnGetAsync ou OnGet é chamado em uma Página do Razor para inicializar o estado da página. Nesse
caso, OnGetAsync obtém uma lista de filmes e os exibe.
Quando OnGet retorna void ou OnGetAsync retorna Task , então nenhum método de retorno é usado. Quando
o tipo de retorno for IActionResult ou Task<IActionResult> , é necessário fornecer uma instrução de retorno. Por
exemplo, o método OnPostAsync do arquivo Pages/Movies/Create.cshtml.cs:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<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
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.DisplayFor na página.
A página de layout
Selecione os links de menu (RazorPagesMovie, Página Inicial e Privacidade). Cada página mostra o mesmo
layout de menu. O layout de menu é implementado no arquivo Pages/Shared/_Layout.cshtml. Abra o arquivo
Pages/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 visualizações específicas da página criadas são mostradas, encapsuladas na página de layout. Por
exemplo, se você selecionar o link Privacidade, a exibição Pages/Privacy.cshtml será renderizada dentro do
método RenderBody .
ViewData e layout
Considere o seguinte código do arquivo Pages/Movies/Index.cshtml:
@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 que não é exibida no arquivo de layout. Ao
contrário de comentários HTML ( <!-- --> ), comentários do Razor não são enviados ao cliente.
Atualizar o layout
Altere o elemento <title> no arquivo Pages/Shared/_Layout.cshtml para exibir Movie, em vez de
RazorPagesMovie.
<!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 . O valor do atributo asp-area está vazio e, portanto, a área não é usada no link. Confira Áreas
para obter mais informações.
Salve suas alterações e teste o aplicativo clicando no link RpMovie. Confira o arquivo _Layout.cshtml no GitHub
caso tenha problemas.
Teste os outros links (Home, RpMovie, Create, Edit e Delete). 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.
NOTE
Talvez você não consiga inserir casas decimais ou vírgulas no campo Price . Para dar suporte à validação do jQuery para
localidades de idiomas diferentes do inglês que usam uma vírgula (“,”) para um ponto decimal e formatos de data diferentes
do inglês dos EUA, você deve tomar medidas para globalizar o aplicativo. Veja Problema 4076 do GitHub para obter
instruções sobre como adicionar casas decimais.
A marcação anterior define o arquivo de layout como Pages/Shared/_Layout.cshtml para todos os arquivos do
Razor na pasta Pages. Veja Layout para obter mais informações.
O modelo Criar página
Examine o modelo de página Pages/Movies/Create.cshtml.cs:
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Models.RazorPagesMovieContext _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, assim, Page é retornado. Mais adiante no tutorial, você verá o estado de inicialização do método
OnGet . O método Page cria um objeto PageResult que renderiza a página Create.cshtml.
A propriedade Movie usa o atributo [BindProperty] para aceitar o model binding. 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 .
_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. A validação do lado do cliente e a validação de modelo são abordadas mais adiante 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";
}
<h1>Create</h1>
<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-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio
Visual Studio Code
Visual Studio para Mac
O Visual Studio exibe a marca <form method="post"> em uma fonte em negrito diferente usada para 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:
A N T E R IO R : A D IC IO N A R U M P R Ó X IM O : B A S E D E
M ODELO DA DOS
Trabalhar com um banco de dados e o ASP.NET
Core
13/12/2018 • 10 minutes to read • Edit Online
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("RazorPagesMovieContext")));
}
Quando o aplicativo é implantado em um servidor de teste ou de produção, uma variável de ambiente pode ser
usada para definir a cadeia de conexão como um servidor de banco de dados real. Consulte Configuração para
obter mais informações.
Visual Studio
Visual Studio Code
Visual Studio para Mac
Clique com o botão direito do mouse na tabela Movie e selecione Designer de exibição:
Observe o ícone de chave ao lado de ID . Por padrão, o EF cria uma propriedade chamada ID para a chave
primária.
Clique com o botão direito do mouse na tabela Movie e selecione Exibir dados:
Propagar o banco de dados
Crie uma classe chamada SeedData na pasta Models com o seguinte código:
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 RazorPagesMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<RazorPagesMovieContext>>()))
{
// 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.
}
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using RazorPagesMovie.Models;
using System;
using Microsoft.EntityFrameworkCore;
namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
try
{
var context=services.
GetRequiredService<RazorPagesMovieContext>();
context.Database.Migrate();
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
Um aplicativo de produção não chamaria Database.Migrate . Ele é adicionado ao código anterior para evitar a
exceção a seguir quando Update-Database não foi executado:
SqlException: não pode abrir o banco de dados "RazorPagesMovieContext-21" solicitado pelo logon. O logon
falhou. O logon falhou para o usuário 'user name'.
Testar o aplicativo
Visual Studio
Visual Studio Code
Visual Studio para Mac
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:
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
A anotação de dados [Column(TypeName = "decimal(18, 2)")] permite que o Entity Framework Core mapeie o
Price corretamente para a moeda no banco de dados. Para obter mais informações, veja Tipos de Dados.
DataAnnotations é abordado no próximo tutorial. O atributo Display especifica o que deve ser exibido no nome
de um campo (neste caso, “Release Date” em vez de “ReleaseDate”). O atributo DataType especifica o tipo de
dados (Data) e, portanto, as informações de hora armazenadas no campo não são exibidas.
Procure Pages/Movies e focalize um link Editar para ver a URL de destino.
Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo
Pages/Movies/Index.cshtml.
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos
HTML em arquivos do Razor. No código anterior, o AnchorTagHelper gera dinamicamente o valor do atributo
href HTML da página Razor (a rota é relativa), o asp-page e a ID da rota ( asp-route-id ). Consulte Geração de
URL para Páginas para obter mais informações.
Use Exibir Código-fonte em seu navegador favorito para examinar a marcação gerada. Uma parte do HTML
gerado é mostrada abaixo:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Os links gerados dinamicamente passam a ID de filme com uma cadeia de consulta (por exemplo, o ?id=1 em
https://localhost:5001/Movies/Details?id=1 ).
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?}"
Com a diretiva @page "{id:int}" , o ponto de interrupção nunca é atingido. O mecanismo de roteamento retorna
HTTP 404. Usando @page "{id:int?}" , o método OnGetAsync retorna NotFound (HTTP 404).
Embora não seja recomendado, você pode escrever o método OnGetAsync (em Pages/Movies/Delete.cshtml.cs)
como:
if (Movie == null)
{
return NotFound();
}
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
O código anterior detecta exceções de simultaneidade quando um cliente exclui o filme e o outro cliente posta
alterações no filme.
Para testar o bloco catch :
Definir um ponto de interrupção em catch (DbUpdateConcurrencyException)
Selecione Editar para um filme, faça alterações, mas não insira Salvar.
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 talvez deseje detectar conflitos de simultaneidade. Confira Lidar com conflitos de
simultaneidade para obter mais informações.
Análise de postagem e associação
Examine o arquivo Pages/Movies/Edit.cshtml.cs:
public class EditModel : PageModel
{
private readonly RazorPagesMovieContext _context;
[BindProperty]
public Movie Movie { get; set; }
if (Movie == null)
{
return NotFound();
}
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Movie.Any(e => e.ID == Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
}
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 convertido em uma data),
o formulário será mostrado 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.
A N T E R IO R : T R A B A L H A N D O C O M U M B A N C O D E P R Ó X IM O : A D IC IO N A R
DA DOS P E S Q U IS A
Adicionar a pesquisa às Páginas Razor do ASP.NET
Core
10/01/2019 • 8 minutes to read • Edit Online
SearchString : contém o texto que os usuários inserem na caixa de texto de pesquisa. SearchString está
decorada com o atributo [BindProperty] . [BindProperty] associa valores de formulário e cadeias de consulta
ao mesmo nome da propriedade. (SupportsGet = true) é necessário para a associação em solicitações GET.
Genres : contém a lista de gêneros. Genres permite que o usuário selecione um gênero na lista. SelectList
exige using Microsoft.AspNetCore.Mvc.Rendering;
MovieGenre : contém o gênero específico que o usuário seleciona (por exemplo, "Oeste").
Genres e MovieGenre são abordados mais adiante neste tutorial.
WARNING
Por motivos de segurança, você deve aceitar 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 a associação de GET é útil ao lidar com
cenários que contam com a cadeia de caracteres de consulta ou com os valores de rota.
Para associar uma propriedade às solicitações GET , defina a propriedade SupportsGet do atributo [BindProperty] como
true : [BindProperty(SupportsGet = true)]
A primeira linha do método OnGetAsync cria uma consulta LINQ para selecionar os filmes:
// using System.Linq;
var movies = from m in _context.Movie
select m;
A consulta é somente definida neste ponto; ela não foi executada no banco de dados.
Se a propriedade SearchString não é nula nem vazia, a consulta de filmes é modificada para filtrar a cadeia 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 da ordenação. No SQL Server, Contains é
mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. No SQLite, com a ordenação 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, https://localhost:5001/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, https://localhost:5001/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.
O tempo de execução do ASP.NET Core usa o model binding para definir o valor da propriedade SearchString
na cadeia de consulta ( ?searchString=Ghost ) ou nos dados de rota ( https://localhost:5001/Movies/Ghost ). O
model binding não diferencia maiúsculas de minúsculas.
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";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
@*Markup removed for brevity.*@
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.
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
@*Markup removed for brevity.*@
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
Adicionar um novo campo em uma página Razor no
ASP.NET Core
10/01/2019 • 10 minutes to read • Edit Online
Crie o aplicativo.
Edite Pages/Movies/Index.cshtml e adicione um campo Rating :
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie)
{
<tr><td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
O aplicativo não funcionará até que o BD seja atualizado para incluir o novo campo. Se for executado agora, o
aplicativo gerará uma SqlException :
SqlException: Invalid column name 'Rating'.
Esse erro é causado devido à classe de modelo Movie atualizada ser diferente do esquema da tabela Movie do
banco de dados. (Não há nenhuma coluna Rating na tabela de banco de dados.)
Existem algumas abordagens para resolver o erro:
1. Faça com que o Entity Framework remova automaticamente e recrie o banco de dados usando o novo
esquema de classe de modelo. Essa abordagem é conveniente no início do ciclo de desenvolvimento; ela
permite que você desenvolva rapidamente o modelo e o esquema de banco de dados juntos. A
desvantagem é que você perde os dados existentes no banco de dados. Não use 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, defina um ponto de interrupção no método SeedData.Initialize .
A N T E R IO R : A D IC IO N A R 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
Adicionar validação a uma Página Razor do ASP.NET
Core
06/02/2019 • 15 minutes to read • Edit Online
Validação
Um princípio-chave do desenvolvimento de software é chamado DRY (“Don't Repeat Yourself”). O Razor Pages
incentiva 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.
Fazer com que o código seja menos propenso a erros e mais fácil de ser testado e mantido.
O suporte de validação fornecido pelo Razor Pages 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 Models/Movie.cs. DataAnnotations fornece um conjunto interno de atributos de validação que são
aplicados de forma declarativa a uma classe ou propriedade. DataAnnotations também contém atributos de
formatação como DataType , que ajudam com a formatação e não fornecem validação.
Atualize a classe Movie para aproveitar os atributos de validação Required , StringLength , RegularExpression e
Range .
public class Movie
{
public int ID { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
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-Z0-9""'\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. O Razor
Pages criado neste tutorial selecionou 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. É possível fazer isso usando as ferramentas para desenvolvedores do
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 ao
Razor Page que edita 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.
A anotação de dados [Column(TypeName = "decimal(18, 2)")] é necessária para que o Entity Framework Core
possa mapear corretamente o Price para a moeda no banco de dados. Para obter mais informações, veja Tipos
de Dados.
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.
O seguinte código mostra como combinar atributos em uma linha:
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)]
public string Rating { get; set; }
}
A Introdução ao Razor Pages e ao EF Core mostra operações mais avançadas do EF Core com o Razor Pages.
Publicar no Azure
Para obter informações sobre como implantar no Azure, consulte Tutorial: Compilar um aplicativo ASP.NET no
Azure com o Banco de Dados SQL. Essas instruções são para um aplicativo ASP.NET, não um aplicativo ASP.NET
Core, mas as etapas são as mesmas.
Obrigado por concluir esta introdução ao Razor Pages. A Introdução ao Razor Pages e ao EF Core é um excelente
acompanhamento para este tutorial.
Recursos adicionais
Auxiliares de marca em formulários no ASP.NET Core
Globalização e localização no ASP.NET Core
Auxiliares de Marca no ASP.NET Core
Auxiliares de marca de autor no ASP.NET Core
A N T E R IO R : A D IC IO N A N D O U M N O V O
CAM PO
Métodos de filtro para Páginas Razor no ASP.NET
Core
30/10/2018 • 7 minutes to read • Edit Online
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace PageFilter.Filters
{
public class SampleAsyncPageFilter : IAsyncPageFilter
{
private readonly ILogger _logger;
No código anterior, ILogger não é necessário. Ele é usado na amostra para fornecer informações de rastreamento
do aplicativo.
O código a seguir habilita o SampleAsyncPageFilter na classe Startup :
namespace PageFilter
{
public class Startup
{
ILogger _logger;
public Startup(ILoggerFactory loggerFactory, IConfiguration configuration)
{
_logger = loggerFactory.CreateLogger<GlobalFiltersLogger>();
Configuration = configuration;
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
}
}
namespace PageFilter.Filters
{
public class SamplePageFilter : IPageFilter
{
private readonly ILogger _logger;
namespace PageFilter.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger _logger;
namespace PageFilter.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;
[AddHeader("Author", "Rick")]
public class ContactModel : PageModel
{
private readonly ILogger _logger;
Confira Substituindo a ordem padrão para obter instruções sobre a substituição da ordem.
Confira Cancelamento e curto-circuito para obter instruções para causar um curto-circuito no pipeline do filtro
por meio de um filtro.
namespace PageFilter.Pages
{
[Authorize]
public class ModelWithAuthFilterModel : PageModel
{
public IActionResult OnGet() => Page();
}
}
Criar a interface do usuário reutilizável usando o
projeto de biblioteca de classes Razor no ASP.NET
Core
09/12/2018 • 10 minutes to read • Edit Online
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.2" />
</ItemGroup>
</Project>
<p>RazorUIClassLib\Areas\MyFeature\Pages\Shared\_Message.cshtml</p>
Para obter mais informações sobre viewimports. cshtml, consulte importando diretivas compartilhadas
Crie a biblioteca de classes para verificar se não há nenhum erro de compilador:
<body>
<partial name="_Header">
@RenderBody()
<partial name="_Footer">
</body>
Convenções de rota e aplicativo das Páginas do
Razor no ASP.NET Core
30/10/2018 • 33 minutes to read • Edit Online
Convenções de ação da rota de página Adicione um modelo de rota às páginas em uma pasta e a
AddFolderRouteModelConvention uma única página.
AddPageRouteModelConvention
AddPageRoute
Convenções de ação do modelo de página Adicione um cabeçalho às páginas em uma pasta, adicione
AddFolderApplicationModelConvention um cabeçalho a uma única página e configure um alocador de
AddPageApplicationModelConvention 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 Substitua o provedor de modelo de página padrão para
alterar as convenções de nomes de manipulador.
Convenções de ação da rota de página Adicione um modelo de rota às páginas em uma pasta e a
AddFolderRouteModelConvention uma única página.
AddPageRouteModelConvention
AddPageRoute
Convenções de ação do modelo de página Adicione um cabeçalho às páginas em uma pasta, adicione
AddFolderApplicationModelConvention um cabeçalho a uma única página e configure um alocador de
AddPageApplicationModelConvention 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 Substitua o provedor de modelo de página padrão para
alterar as convenções de nomes de manipulador.
Ordem de rota
As rotas especificam um Order para processamento (rota correspondente).
PEDIDO COMPORTAMENTO
Convenções de modelo
Adicione um representante para IPageConvention para adicionar as convenções de modelo que se aplicam às
Páginas Razor.
Adicionar uma convenção de modelo de rota para todas as páginas
Use Convenções para criar e adicionar um IPageRouteModelConvention à coleção de instâncias de
IPageConvention que são aplicadas durante a construção do modelo de rota de página.
O aplicativo de exemplo adiciona um modelo de rota {globalTemplate?} a todas as páginas no aplicativo:
A propriedade Order do AttributeRouteModel é definida como 1 . Isso garante que a rota correspondência de
comportamento no aplicativo de exemplo a seguir:
Um modelo de rota para TheContactPage/{text?} é adicionado posteriormente no tópico. A rota de página
Contact tem uma ordem padrão de null ( Order = 0 ), para que ele corresponda ao antes do
{globalTemplate?} modelo de rota.
Um {aboutTemplate?} modelo de rota é adicionado posteriormente no tópico. O modelo {aboutTemplate?}
recebe uma Order de 2 . Quando a página About é solicitada no /About/RouteDataValue , "RouteDataValue" é
carregado no RouteData.Values["globalTemplate"] ( Order = 1 ) e não RouteData.Values["aboutTemplate"] (
Order = 2 ) devido à configuração da propriedade Order .
Um {otherPagesTemplate?} modelo de rota é adicionado posteriormente no tópico. O modelo
{otherPagesTemplate?} recebe uma Order de 2 . Quando qualquer página na páginas/OtherPages pasta for
solicitada com um parâmetro de rota (por exemplo, /OtherPages/Page1/RouteDataValue ), "RouteDataValue" é
carregado no RouteData.Values["globalTemplate"] ( Order = 1 ) e não RouteData.Values["otherPagesTemplate"]
( Order = 2 ) devido à configuração de Order propriedade.
Sempre que possível, não defina a Order , que resulta em Order = 0 . Dependem de roteamento para selecionar
a rota correta.
As opções de Páginas Razor, como a adição de Convenções, são adicionados quando o MVC é adicionado à
coleção de serviços em Startup.ConfigureServices . Para obter um exemplo, confira o aplicativo de exemplo.
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:
Startup.ConfigureServices :
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.Add(new GlobalPageHandlerModelConvention());
});
A propriedade Order do AttributeRouteModel é definida como 2 . Isso garante que o modelo para
{globalTemplate?} (definido anteriormente no tópico para 1 ) tem prioridade para os dados de rota a primeira
posição de valor quando um único valor de rota é fornecido. Se uma página na páginas/OtherPages pasta for
solicitada com um valor de parâmetro de rota (por exemplo, /OtherPages/Page1/RouteDataValue ),
"RouteDataValue" é carregado no RouteData.Values["globalTemplate"] ( Order = 1 ) e não
RouteData.Values["otherPagesTemplate"] ( Order = 2 ) devido à configuração de Order propriedade.
Sempre que possível, não defina a Order , que resulta em Order = 0 . Dependem de roteamento para selecionar
a rota correta.
Solicite a página Page1 da amostra em localhost:5000/OtherPages/Page1/GlobalRouteValue/OtherPagesRouteValue e
inspecione o resultado:
A propriedade Order do AttributeRouteModel é definida como 2 . Isso garante que o modelo para
{globalTemplate?} (definido anteriormente no tópico para 1 ) tem prioridade para os dados de rota a primeira
posição de valor quando um único valor de rota é fornecido. Se a página About é solicitada com um valor de
parâmetro de rota no /About/RouteDataValue , "RouteDataValue" é carregado no
RouteData.Values["globalTemplate"] ( Order = 1 ) e não RouteData.Values["aboutTemplate"] ( Order = 2 ) devido à
configuração de Order propriedade.
Sempre que possível, não defina a Order , que resulta em Order = 0 . Dependem de roteamento para selecionar
a rota correta.
Solicite a página About da amostra em localhost:5000/About/GlobalRouteValue/AboutRouteValue e inspecione o
resultado:
// Slugify value
return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
}
}
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:[email protected]">[email protected]</a><br>
<strong>Marketing:</strong> <a href="mailto:[email protected]">[email protected]</a>
</address>
<p>@Model.RouteDataTextTemplateValue</p>
Observe que a URL gerada para o link Contato na página renderizada reflete a rota atualizada:
Visite a página Contact em sua rota comum, /Contact , ou na rota personalizada, /TheContactPage . Se você
fornecer um segmento de rota text adicional, a página mostrará o segmento codificado em HTML fornecido:
Convenções de ação do modelo de página
O provedor de modelo de página padrão que implementa IPageApplicationModelProvider invoca convenções
que foram projetadas para fornecer pontos de extensibilidade para configuração de modelos de página. Essas
convenções são úteis ao criar e modificar cenários de descoberta e processamento de página.
Para os exemplos desta seção, o aplicativo de exemplo usa uma classe AddHeaderAttribute , que é um
ResultFilterAttribute, aplicável a um cabeçalho de resposta:
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"
class="btn btn-danger"
asp-route-id="@message.Id">Delete</button>
</li>
}
</ol>
</form>
</div>
Recursos adicionais
Convenções de autorização de Páginas Razor
Carregar arquivos para uma Página Razor no
ASP.NET Core
09/12/2018 • 26 minutes to read • Edit Online
WARNING
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.
using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class FileUpload
{
[Required]
[Display(Name="Title")]
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Required]
[Display(Name="Public Schedule")]
public IFormFile UploadPublicSchedule { get; set; }
[Required]
[Display(Name="Private Schedule")]
public IFormFile UploadPrivateSchedule { get; set; }
}
}
using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class FileUpload
{
[Required]
[Display(Name="Title")]
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Required]
[Display(Name="Public Schedule")]
public IFormFile UploadPublicSchedule { get; set; }
[Required]
[Display(Name="Private Schedule")]
public IFormFile UploadPrivateSchedule { get; set; }
}
}
A classe tem uma propriedade para o título do agendamento e uma propriedade para cada uma das duas versões
do agendamento. Todas as três propriedades são necessárias e o título deve ter 3-60 caracteres.
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Net;
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;
}
}
}
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;
}
}
}
return RedirectToPage("./Index");
}
O processo de trabalho deve ter permissões de gravação para o local especificado por filePath .
NOTE
O filePath precisa incluir o nome do arquivo. Se o nome do arquivo não for fornecido, uma UnauthorizedAccessException
será gerada no tempo de execução.
WARNING
Nunca persista os arquivos carregados na mesma árvore de diretório que o aplicativo.
O exemplo de código não oferece proteção do lado do servidor contra carregamentos de arquivos mal-intencionados. Para
obter informações de como reduzir a área da superfície de ataque ao aceitar arquivos de usuários, confira os seguintes
recursos:
Unrestricted File Upload (Carregamento de arquivo irrestrito)
Segurança do Azure: Verifique se os controles adequados estão em vigor ao aceitar arquivos de usuários
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Schedule
{
public int ID { get; set; }
public string Title { get; set; }
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 RazorPagesMovieContext
Especifique um DbSet no RazorPagesMovieContext (Data/RazorPagesMovieContext.cs) para os agendamentos:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace RazorPagesMovie.Models
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext (DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}
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)
{
}
No PMC, execute os seguintes comandos. Estes comandos adicionam uma tabela Schedule ao banco de dados:
Add-Migration AddScheduleTable
Update-Database
@page
@model RazorPagesMovie.Pages.Schedules.IndexModel
@{
ViewData["Title"] = "Schedules";
}
<h2>Schedules</h2>
<hr />
<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>
</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");}
}
@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>
</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)
@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.
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.RazorPagesMovieContext _context;
[BindProperty]
public FileUpload FileUpload { get; set; }
var publicScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPublicSchedule, ModelState);
var privateScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPrivateSchedule, ModelState);
_context.Schedule.Add(schedule);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
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]
[BindProperty]
public FileUpload FileUpload { get; set; }
var publicScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPublicSchedule, ModelState);
var privateScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPrivateSchedule, ModelState);
_context.Schedule.Add(schedule);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
[BindProperty]
public FileUpload FileUpload { get; set; }
[BindProperty]
public FileUpload FileUpload { get; set; }
O modelo também usa uma lista dos agendamentos ( IList<Schedule> ) para exibir os agendamentos armazenados
no banco de dados na página:
public IList<Schedule> Schedule { get; private set; }
Quando a página for carregada com OnGetAsync , Schedules é preenchido com o banco de dados e usado para
gerar uma tabela HTML de agendamentos carregados:
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");
}
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>
@page "{id:int}"
@model RazorPagesMovie.Pages.Schedules.DeleteModel
@{
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.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.RazorPagesMovieContext _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");
}
}
}
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");
}
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.
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:
Solução de problemas
Para solucionar problemas de informações com IFormFile carregar, consulte carregamentos de arquivos no
ASP.NET Core: solução de problemas.
SDK do Razor do ASP.NET Core
26/01/2019 • 8 minutes to read • Edit Online
Pré-requisitos
SDK do .NET Core 2.1 ou posteriores
<Project SDK="Microsoft.NET.Sdk.Razor">
...
</Project>
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
</ItemGroup>
</Project>
WARNING
O Microsoft.AspNetCore.Razor.Design e Microsoft.AspNetCore.Mvc.Razor.Extensions pacotes são incluídos na
metapacote Microsoft. No entanto, o menor de versão Microsoft.AspNetCore.App referência de pacote fornece um
metapacote para o aplicativo que não inclui a versão mais recente do Microsoft.AspNetCore.Razor.Design . Projetos
devem fazer referência a uma versão consistente do Microsoft.AspNetCore.Razor.Design (ou
Microsoft.AspNetCore.Mvc ) para que as correções mais recentes do tempo de compilação para Razor são incluídas. Para
obter mais informações, consulte esse problema de GitHub.
Propriedades
As seguintes propriedades controlam o comportamento do SDK do Razor como parte de um build de projeto:
RazorCompileOnBuild – Quando true , compila e emite o assembly Razor como parte da criação do projeto.
Assume o padrão de true .
RazorCompileOnPublish – Quando true , compila e emite o assembly Razor como parte da publicação do
projeto. Assume o padrão de true .
As propriedades e os itens na tabela a seguir são usados para configurar entradas e saídas para o SDK do Razor.
ITENS DESCRIÇÃO
PROPRIEDADE DESCRIÇÃO
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.
NOTE
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.
NOTE
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).
NOTE
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.
TIP
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] é
o atributo usado para criar filtros de autorização do MVC.
[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
O uso pela estrutura da injeção de dependência e de interfaces a torna adequada para teste de unidade. Além
disso, a estrutura inclui recursos (como um provedor TestHost e InMemory para o Entity Framework) que
também agiliza e facilita a execução de testes de integração. 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>
Versão de compatibilidade
O método SetCompatibilityVersion permite que um aplicativo aceite ou recuse as possíveis alterações da falha
de comportamento introduzidas no ASP.NET Core MVC 2.1 ou posteriores.
Para obter mais informações, consulte Versão de compatibilidade do ASP.NET Core MVC.
Criar um aplicativo Web com o ASP.NET Core MVC
23/01/2019 • 2 minutes to read • Edit Online
Este tutorial ensina a usar o desenvolvimento Web do ASP.NET Core MVC com controladores e exibições. Se você
é novo no desenvolvimento da Web ASP.NET Core, considere a versão Razor Pages deste tutorial, que oferece um
ponto inicial mais simples.
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
10/01/2019 • 10 minutes to read • Edit Online
NOTE
Estamos testando a usabilidade de uma nova estrutura proposta para o sumário do ASP.NET Core. Se você tiver alguns
minutos para experimentar um exercício de localização de sete tópicos diferentes no sumário atual ou proposto, clique aqui
para participar do estudo.
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
SDK 2.2 ou posterior do .NET Core
Faça as configurações necessárias na caixa de diálogo Novo aplicativo Web ASP.NET Core (.NET Core) –
MvcMovie:
Na caixa de lista suspensa do seletor de versão, selecione ASP.NET Core 2.2
Selecione Aplicativo Web (Model-View-Controller)
Selecione OK.
O Visual Studio usou um modelo padrão para o projeto MVC que você acabou de criar. Para que o aplicativo
comece a funcionar agora mesmo, digite um nome de projeto e selecione algumas opções. Este é um projeto
inicial básico e é um bom ponto de partida.
Pressione Ctrl+F5 para executar o aplicativo no modo sem depuração.
O Visual Studio inicia o IIS Express e executa o aplicativo. Observe que a barra de endereços mostra
localhost:port# e não algo como example.com . Isso ocorre porque localhost é o nome do host padrão do
computador local. Quando o Visual Studio cria um projeto Web, uma porta aleatória é usada para o servidor
Web. Na imagem acima, o número da porta é 5000. A URL no navegador mostra localhost:5000 . Quando
você executar o aplicativo, verá um número de porta diferente.
Iniciar o aplicativo com Ctrl+F5 (modo de não depuração) permite que você faça alterações de código, salve o
arquivo, atualize o navegador e veja as alterações de código. Muitos desenvolvedores preferem usar modo de
não depuração para iniciar o aplicativo e exibir alterações rapidamente.
Você pode iniciar o aplicativo no modo de não depuração ou de depuração por meio do item de menu
Depurar:
Você pode depurar o aplicativo selecionando o botão IIS Express
Selecione Aceitar para dar consentimento de rastreamento. Este aplicativo não acompanha informações
pessoais. O código de modelo gerado inclui ativos para ajudar a cumprir o RGPD (Regulamento Geral
sobre a Proteção de Dados).
A VA N ÇA R
Adicionar um controlador a um aplicativo ASP.NET
Core MVC
10/01/2019 • 12 minutes to read • Edit Online
Adicionar um controlador
Visual Studio
Visual Studio Code
Visual Studio para Mac
No Gerenciador de Soluções, clique com o botão direito do mouse em Controladores > Adicionar >
Controlador
Na caixa de diálogo Adicionar Scaffold, selecione Controlador MVC – Vazio
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
https://localhost:5001/HelloWorld , e combina o protocolo usado HTTPS , o local de rede do servidor Web
(incluindo a porta TCP ) localhost:5001 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 primeiro comentário especifica um método HTTP GET invocado por meio do
acréscimo de /HelloWorld/Welcome/ à URL base. Mais adiante no tutorial, o mecanismo de scaffolding será usado
para gerar métodos HTTP POST que atualizam dados.
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ê acessa 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. Os dados de rota são explicados
posteriormente no tutorial.
Navegue para https://localhost:xxxx/HelloWorld/Welcome . O método Welcome é executado e retorna a cadeia de
caracteres This is the Welcome action method... . 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 em $"Hello {name}, NumTimes is: {numTimes}" .
(Substitua xxxx pelo número da porta.) Você pode tentar valores diferentes para name e numtimes na URL. O
sistema de model binding do MVC mapeia automaticamente os parâmetros nomeados da cadeia de consulta na
barra de endereços para os parâmetros no método. Consulte Model binding 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 correspondeu 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.
A N T E R IO R P R Ó X IM O
Adicionar uma exibição a um aplicativo ASP.NET
Core MVC
04/02/2019 • 15 minutes to read • Edit Online
O código anterior chama o método View do controlador. Ele usa um modelo de exibição para gerar uma resposta
HTML. 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 string .
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<!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 include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-
bootstrap/4.1.3/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"
crossorigin="anonymous"
integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
</environment>
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-
shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-controller="Movies" asp-action="Index">Movie App</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-
collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-
action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-
action="Privacy">Privacy</a>
</li>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<partial name="_CookieConsentPartial" />
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-
bootstrap/4.1.3/js/bootstrap.bundle.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
</script>
</environment>
<script src="~/js/site.js" asp-append-version="true"></script>
Na marcação anterior, o asp-area atributo do Auxiliar de Marca de Âncora foi omitido porque este aplicativo não
está usando Áreas.
Observação: O controlador Movies não foi implementado. Neste ponto, o link Movie App não está funcionando.
Salve suas alterações e selecione o link Privacidade. Observe como o título na guia do navegador agora exibe
Política de Privacidade – Aplicativo de filme, em vez de Política de Privacidade – Filme MVC:
Selecione o link Página Inicial 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";
}
@{
ViewData["Title"] = "Movie List";
}
O título e o elemento <h2> são ligeiramente diferentes para que possa ver qual parte do código altera a exibição.
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.
No exemplo acima, o dicionário ViewData foi usado para passar dados do controlador para uma exibição. Mais
adiante no tutorial, um modelo de exibição será usado 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 . Para obter mais informações, confira Quando usar ViewBag, ViewData ou TempData .
No próximo tutorial, será criado um banco de dados de filmes.
A N T E R IO R P R Ó X IM O
Adicione um modelo a um aplicativo ASP.NET Core
MVC
21/01/2019 • 24 minutes 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; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Na caixa de diálogo Adicionar Scaffold, selecione Controlador MVC com exibições, usando o Entity
Framework > Adicionar.
Preencha a caixa de diálogo Adicionar Controlador:
Classe de modelo: Movie (MvcMovie.Models)
Classe de contexto de dados: selecione o ícone + e adicione o MvcMovie.Models.MvcMovieContext
padrão
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.
Migração inicial
Visual Studio
Visual Studio Code/Visual Studio para Mac
Nesta seção, o PMC (Console de Gerenciador de Pacotes) é usado para:
Adicione uma migração inicial.
Atualize o banco de dados com a migração inicial.
No menu Ferramentas, selecione Gerenciador de pacotes NuGet > Console do Gerenciador de pacotes.
No PMC, insira os seguintes comandos:
Add-Migration Initial
Update-Database
O comando Add-Migration gera código para criar o esquema de banco de dados inicial.
Os comandos anteriores geram o seguinte aviso: “Nenhum tipo foi especificado para a coluna decimal "Preço" no
tipo de entidade "Filme". Isso fará com que valores sejam truncados silenciosamente se não couberem na
precisão e na escala padrão. Especifique explicitamente o tipo de coluna do SQL Server que pode acomodar
todos os valores usando 'HasColumnType()'.”
Você pode ignorar esse aviso, ele será corrigido em um tutorial posterior.
O esquema é baseado no modelo especificado no DbContext (no arquivo Models/MvcMovieContext.cs). O
argumento InitialCreate é usado para nomear as migrações. Qualquer nome pode ser usado, mas, por
convenção, um nome que descreve a migração é selecionado.
O comando ef database update executa o método Up no arquivo Migrations/<time-stamp>_InitialCreate.cs. O
método Up cria o banco de dados.
Visual Studio
Visual Studio Code/Visual Studio para Mac
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}
O MvcMovieContext coordena a funcionalidade do EF Core (Criar, Ler, Atualizar, Excluir etc.) para o modelo Movie
. O contexto de dados ( MvcMovieContext ) deriva de Microsoft.EntityFrameworkCore.DbContext. O contexto de
dados especifica quais entidades são incluídas no modelo de dados:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace MvcMovie.Models
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
O código anterior cria uma propriedade DbSet<Movie> para o conjunto de entidades. Na terminologia do Entity
Framework, um conjunto de entidades normalmente corresponde a uma tabela de banco de dados. Uma entidade
corresponde a uma linha da tabela.
O nome da cadeia de conexão é passado para o contexto com a chamada de um método em um objeto
DbContextOptions. Para o desenvolvimento local, o sistema de configuração do ASP.NET Core lê a cadeia de
conexão do arquivo appsettings.json.
O esquema é baseado no modelo especificado no MvcMovieContext (no arquivo Data/MvcMovieContext.cs). O
argumento Initial é usado para nomear as migrações. Qualquer nome pode ser usado, mas, por convenção,
um nome que descreve a migração é usado. Consulte Introdução às migrações para obter mais informações.
O comando Update-Database executa o método Up no arquivo Migrations/{time-stamp }_InitialCreate.cs, que cria
o banco de dados.
Testar o aplicativo
Executar o aplicativo e acrescentar /Movies à URL no navegador ( http://localhost:port/movies ).
NOTE
Talvez você não consiga inserir casas decimais ou vírgulas no campo Price . Para dar suporte à validação do jQuery
para localidades com idiomas diferentes do inglês que usam uma vírgula (",") para um ponto decimal e formatos de
data diferentes do inglês dos EUA, o aplicativo precisa ser globalizado. Para obter instruções sobre a globalização,
consulte esse problema no GitHub.
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
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:
services.AddDbContext<MvcMovieContext>(options => especifica o banco de dados a ser usado e a cadeia de
conexão.
=> é um operador lambda
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 FirstOrDefaultAsync 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);
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class="col-sm-10">
@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, a seguinte instrução @model foi
automaticamente incluída 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";
}
<h1>Index</h1>
<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 do tempo de compilação do
código:
Recursos adicionais
Auxiliares de marcação
Globalização e localização
A N T E R IO R – A D IC IO N A N D O U M A P R Ó X IM O – T R A B A L H A N D O C O M O
E X IB IÇ Ã O SQL
Trabalhar com o SQL no ASP.NET Core
10/01/2019 • 6 minutes to read • Edit Online
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
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.
Visual Studio
Visual Studio Code/Visual Studio para Mac
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-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.
}
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
using MvcMovie;
namespace MvcMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
try
{
var context = services.GetRequiredService<MvcMovieContext>();
context.Database.Migrate();
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
Visual Studio
Visual Studio Code/Visual Studio para Mac
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.
A N T E R IO R P R Ó X IM O
Os métodos e as exibições do controlador no
ASP.NET Core
16/01/2019 • 15 minutes to read • Edit Online
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.
A anotação de dados [Column(TypeName = "decimal(18, 2)")] é necessária para que o Entity Framework Core
possa mapear corretamente o Price para a moeda no banco de dados. Para obter mais informações, veja Tipos
de Dados.
Procure o controlador Movies e mantenha o ponteiro do mouse pressionado sobre um link Editar para ver a
URL de destino.
Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora do MVC Core no arquivo
Views/Movies/Index.cshtml.
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos
HTML em arquivos do Razor. No código acima, o AnchorTagHelper gera dinamicamente o valor do atributo href
HTML com base na ID de rota e no método de ação do controlador. Use a opção Exibir Código-fonte em seu
navegador favorito ou as ferramentas do desenvolvedor para examinar a marcação gerada. Uma parte do HTML
gerado é mostrada abaixo:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
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);
}
// 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. Para obter mais informações, consulte Proteger o
controlador contra o excesso de postagem. 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] .
[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(nameof(Index));
}
return View(movie);
}
// 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";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe como o modelo de exibição tem uma instrução @model MvcMovie.Models.Movie na parte superior do
arquivo. @model MvcMovie.Models.Movie especifica que a exibição espera que o modelo de exibição seja do tipo
Movie .
O código com scaffolding usa vários métodos de Auxiliares de Marcação para simplificar a marcação HTML. O –
Auxiliar de Marcação de Rótulo exibe o nome do campo (“Title”, “ReleaseDate”, “Genre” ou “Price”). O Auxiliar de
Marcação de Entrada renderiza um elemento <input> HTML. O Auxiliar de Marcação de Validação exibe todas as
mensagens de validação associadas a essa propriedade.
Execute o aplicativo e navegue para a URL /Movies . Clique em um link Editar. No navegador, exiba a origem da
página. O HTML gerado para o elemento <form> é mostrado abaixo.
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID"
value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-
replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must
be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-
replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden"
value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-
MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
Os elementos <input> estão em um elemento HTML <form> cujo atributo action está definido para ser postado
para a URL /Movies/Edit/id . Os dados de formulário serão postados com o servidor quando o botão Save
receber um clique. A última linha antes do elemento </form> de fechamento mostra o token XSRF oculto gerado
pelo Auxiliar de Marcação de Formulário.
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
O atributo [ValidateAntiForgeryToken] valida o token XSRF oculto gerado pelo gerador de tokens antifalsificação
no Auxiliar de Marcação de Formulário
O sistema de model binding 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 forem 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
Auxiliares de marca de autor
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 P R Ó X IM O
Adicionar a pesquisa a um aplicativo ASP.NET Core
MVC
10/01/2019 • 12 minutes 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 da ordenação. No SQL
Server, Contains é mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. No SQLite, com a
ordenação 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?}");
});
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. Agora você adicionará os elementos da interface do usuário para ajudá-los a filtrar 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));
}
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. Corrigimos isso
especificando que a solicitação deve ser HTTP GET :
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
Title: <input type="text" name="SearchString">
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
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; }
public string SearchString { get; set; }
}
}
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
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.
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<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
Adicionar um novo campo a um aplicativo ASP.NET
Core MVC
14/01/2019 • 7 minutes to read • Edit Online
[Bind("ID,Title,ReleaseDate,Genre,Price,Rating")]
Atualize 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 :
<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>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "R",
Price = 7.99M
},
O aplicativo não funcionará até que o BD seja atualizado para incluir o novo campo. Se ele tiver sido executado
agora, o seguinte SqlException será gerado:
SqlException: Invalid column name 'Rating'.
Este erro ocorre 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. 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.
Isso é uma boa abordagem para o desenvolvimento inicial e ao usar o SQLite.
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, as Migrações do Code First são usadas.
Visual Studio
Visual Studio Code/Visual Studio para Mac
No menu Ferramentas, selecione Gerenciador de Pacotes NuGet > Console do Gerenciador de Pacotes.
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 método de inicialização propagará o BD e incluirá o campo Rating .
Execute o aplicativo e verifique se você pode criar/editar/exibir filmes com um campo Rating . Você deve
adicionar o campo Rating aos modelos de exibição Edit , Details e Delete .
A N T E R IO R P R Ó X IM O
Adicionar a validação a um aplicativo ASP.NET Core
MVC
10/01/2019 • 18 minutes to read • Edit Online
Atualize a classe Movie para aproveitar os atributos de validação Required , StringLength , RegularExpression e
Range internos.
public class Movie
{
public int Id { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
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-Z0-9""'\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 (primeira letra maiúscula, 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 Core ajuda a tornar seu 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.
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.
A imagem a seguir mostra como desabilitar o JavaScript no navegador Chrome.
<h4>Movie</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="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
a marcação anterior é usada pelos métodos de ação para exibir o formulário inicial e exibi-lo novamente em caso
de erro.
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 (ou "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).
NOTE
A validação do jQuery não funciona com os atributos Range e DateTime . Por exemplo, o seguinte código sempre exibirá
um erro de validação do lado do cliente, mesmo quando a data estiver no intervalo especificado:
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
Você precisará desabilitar a validação de data do jQuery para usar o atributo Range com DateTime . Geralmente,
não é uma boa prática compilar datas rígidas nos modelos e, portanto, o uso do atributo Range e de DateTime
não é recomendado.
O seguinte código mostra como combinar atributos em uma linha:
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\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
Auxiliares de marca de autor
A N T E R IO R P R Ó X IM O
Examine os métodos Details e Delete de um
aplicativo ASP.NET Core
04/02/2019 • 5 minutes 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.FindAsync(id);
_context.Movie.Remove(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(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 uma 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
Para obter informações sobre como implantar no Azure, consulte Tutorial: criar um aplicativo Web do .NET Core e
do Banco de Dados SQL no Serviço de Aplicativo do Azure.
A N T E R IO R
Exibições no ASP.NET Core MVC
26/12/2018 • 26 minutes to read • Edit Online
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.
<h2>Contact</h2>
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>
return View(viewModel);
}
Não há restrições quanto aos tipos de modelo que você pode fornecer a uma exibição. Recomendamos o uso de
viewmodels do tipo POCO (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; }
}
}
Nada impede que você use as mesmas classes para seus tipos de viewmodel e seus tipos de modelo de
negócios. No entanto, o uso de modelos separados permite que suas exibições variem independentemente das
partes de lógica de negócios e de acesso a dados do aplicativo. A separação de modelos e viewmodels também
oferece benefícios de segurança quando os modelos usam associação de modelos e validação para dados
enviados ao aplicativo pelo usuário.
Dados fracamente tipados (ViewData, atributo ViewData e ViewBag)
ViewBag não está disponível nas Páginas do Razor.
Além de exibições fortemente tipadas, as exibições têm acesso a uma coleção de dados fracamente tipados
(também chamada de tipagem flexível). Diferente dos tipos fortes, ter tipos fracos (ou tipos flexíveis) significa
que você não declara explicitamente o tipo dos dados que está usando. Você pode usar a coleção de dados
fracamente tipados para transmitir pequenas quantidades de dados para dentro e para fora dos controladores e
das exibições.
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.
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 fracamente tipados. 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();
}
@ViewData["Greeting"] World!
<address>
@address.Name<br>
@address.Street<br>
@address.City, @address.State @address.PostalCode
</address>
Atributo ViewData
Outra abordagem que usa o ViewDataDictionary é ViewDataAttribute. As propriedades nos controladores ou
nos modelos da Página do Razor decoradas com [ViewData] têm seus valores armazenados e carregados do
dicionário.
No exemplo a seguir, o controlador Home contém uma propriedade Title decorada com [ViewData] .O
método About define o título para a exibição About:
return View();
}
}
<h1>@Model.Title</h1>
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
ViewBag
ViewBag não está disponível nas Páginas do Razor.
ViewBag é um objeto DynamicViewData que fornece acesso dinâmico aos objetos armazenados em ViewData .
Pode ser mais conveniente trabalhar com ViewBag , pois ele não requer uma conversão. O exemplo a seguir
mostra como usar ViewBag com o mesmo resultado que o uso de ViewData acima:
public IActionResult SomeAction()
{
ViewBag.Greeting = "Hello";
ViewBag.Address = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};
return View();
}
@ViewBag.Greeting World!
<address>
@ViewBag.Address.Name<br>
@ViewBag.Address.Street<br>
@ViewBag.Address.City, @ViewBag.Address.State @ViewBag.Address.PostalCode
</address>
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.
Por Steve Smith, Luke Latham, Maher JENDOUBI, Rick Anderson e Scott Sauber
Uma exibição parcial é um arquivo de marcação Razor (.cshtml) que renderiza a saída HTML dentro da saída
processada de outro arquivo de marcação.
O termo exibição parcial é usado durante o desenvolvimento de um aplicativo MVC, no qual os arquivos de
marcação são chamados de exibições, ou de um aplicativo Razor Pages, no qual os arquivos de marcação são
chamados de páginas. Este tópico refere-se genericamente a exibições do MVC e a páginas do Razor Pages
como arquivos de marcação.
Exibir ou baixar código de exemplo (como baixar)
Quando houver uma extensão de arquivo, o Auxiliar de Marca fará referência a uma exibição parcial que
precisa estar na mesma pasta que o arquivo de marcação que chama a exibição parcial:
O exemplo a seguir faz referência a uma exibição parcial da raiz do aplicativo. Caminhos que começam com
um til-barra ( ~/ ) ou uma barra ( / ) referem-se à raiz do aplicativo:
Páginas do Razor
MVC
O exemplo a seguir faz referência a uma exibição parcial com um caminho relativo:
<partial name="../Account/_PartialName.cshtml" />
Para obter mais informações, consulte Auxiliar de marca parcial no ASP.NET Core.
Auxiliar HTML assíncrono
Ao usar um Auxiliar HTML, a melhor prática é usar PartialAsync. PartialAsync retorna um tipo IHtmlContent
encapsulado em um Task<TResult>. O método é referenciado prefixando a chamada em espera com um
caractere @ :
@await Html.PartialAsync("_PartialName")
Quando houver a extensão de arquivo, o Auxiliar de HTML fará referência a uma exibição parcial que precisa
estar na mesma pasta que o arquivo de marcação que chama a exibição parcial:
@await Html.PartialAsync("_PartialName.cshtml")
O exemplo a seguir faz referência a uma exibição parcial da raiz do aplicativo. Caminhos que começam com
um til-barra ( ~/ ) ou uma barra ( / ) referem-se à raiz do aplicativo:
Páginas do Razor
@await Html.PartialAsync("~/Pages/Folder/_PartialName.cshtml")
@await Html.PartialAsync("/Pages/Folder/_PartialName.cshtml")
MVC
@await Html.PartialAsync("~/Views/Folder/_PartialName.cshtml")
@await Html.PartialAsync("/Views/Folder/_PartialName.cshtml")
O exemplo a seguir faz referência a uma exibição parcial com um caminho relativo:
@await Html.PartialAsync("../Account/_LoginPartial.cshtml")
Como alternativa, é possível renderizar uma exibição parcial com RenderPartialAsync. Esse método não
retorna um IHtmlContent. Ele transmite a saída renderizada diretamente para a resposta. Como o método não
retorna nenhum resultado, ele precisa ser chamado dentro de um bloco de código Razor:
@{
await Html.RenderPartialAsync("_AuthorPartial");
}
Como RenderPartialAsync transmite conteúdo renderizado, ele apresenta melhor desempenho em alguns
cenários. Em situações cruciais para o desempenho, submeta a página a benchmark usando ambas as
abordagens e use aquela que gera uma resposta mais rápida.
Auxiliar de HTML assíncrono
Partial e RenderPartial são os equivalentes síncronos de PartialAsync e RenderPartialAsync , respectivamente.
Os equivalentes síncronos não são recomendados porque há cenários em que eles realizam deadlock. Os
métodos síncronos estão programados para serem removidos em uma versão futura.
IMPORTANT
Se for necessário executar código, use um componente de exibição em vez de uma exibição parcial.
Chamar Partial ou RenderPartial resulta em um aviso do analisador do Visual Studio. Por exemplo, a
presença de Partial produz a seguinte mensagem de aviso:
Uso de IHtmlHelper.Partial pode resultar em deadlocks de aplicativo. Considere usar o Auxiliar de Marca
<parcial> ou IHtmlHelper.PartialAsync.
Substitua as chamadas para @Html.Partial por @await Html.PartialAsync ou o Auxiliar de Marca Parcial. Para
obter mais informações sobre a migração do auxiliar de marca parcial, consulte Migrar de um auxiliar HTML.
MVC
1. /Areas/<Area-Name>/Views/<Controller-Name>
2. /Areas/<Area-Name>/Views/Shared
3. /Views/Shared
4. /Pages/Shared
1. /Areas/<Area-Name>/Views/<Controller-Name>
2. /Areas/<Area-Name>/Views/Shared
3. /Views/Shared
O exemplo a seguir demonstra como passar uma instância de ViewDataDictionary para uma exibição parcial:
Você pode passar um modelo para uma exibição parcial. O modelo pode ser um objeto personalizado. Você
pode passar um modelo com PartialAsync (renderiza um bloco de conteúdo para o chamador) ou
RenderPartialAsync (transmite o conteúdo para a saída):
Páginas do Razor
A marcação a seguir no aplicativo de exemplo é da página Pages/ArticlesRP/ReadRP.cshtml. A página contém
duas exibições parciais. A segunda exibição parcial passa um modelo e ViewData para a exibição parcial. A
sobrecarga do construtor ViewDataDictionary é usada para passar um novo dicionário ViewData , retendo
ainda o dicionário ViewData existente.
@model ReadRPModel
<h2>@Model.Article.Title</h2>
@* Pass the author's name to Pages\Shared\_AuthorPartialRP.cshtml *@
@await Html.PartialAsync("../Shared/_AuthorPartialRP", Model.Article.AuthorName)
@Model.Article.PublicationDate
@* Loop over the Sections and pass in a section and additional ViewData to
the strongly typed Pages\ArticlesRP\_ArticleSectionRP.cshtml partial view. *@
@{
var index = 0;
index++;
}
}
@using PartialViewsSample.ViewModels
@model ArticleSection
MVC
A marcação a seguir no aplicativo de exemplo mostra a exibição Views/Articles/Read.cshtml. A exibição
contém duas exibições parciais. A segunda exibição parcial passa um modelo e ViewData para a exibição
parcial. A sobrecarga do construtor ViewDataDictionary é usada para passar um novo dicionário ViewData ,
retendo ainda o dicionário ViewData existente.
@model PartialViewsSample.ViewModels.Article
<h2>@Model.Title</h2>
@* Pass the author's 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;
index++;
}
}
@model string
<div>
<h3>@Model</h3>
This partial view from /Views/Shared/_AuthorPartial.cshtml.
</div>
No tempo de execução, as parciais são renderizadas para a saída renderizada do arquivo de marcação pai que,
por sua vez, é renderizada dentro do _Layout.cshtml compartilhado. A primeira exibição parcial renderiza a
data de publicação e o nome do autor do artigo:
Abraham Lincoln
Esta exibição parcial do <caminho do arquivo de exibição parcial compartilhada>. 19/11/1863 12:00:00
AM
Recursos adicionais
Referência da sintaxe Razor para ASP.NET Core
Auxiliares de Marca no ASP.NET Core
Auxiliar de marca parcial no ASP.NET Core
Componentes de exibição no ASP.NET Core
Áreas no ASP.NET Core
Referência da sintaxe Razor para ASP.NET Core
Componentes de exibição no ASP.NET Core
Áreas no ASP.NET Core
Tratar solicitações com controladores no ASP.NET
Core MVC
25/06/2018 • 10 minutes 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á duas 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 em 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:
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:
Tratar erros
Cache de resposta
Muitos interesses paralelos podem ser abordados com filtros ou um middleware personalizado.
Roteamento para ações do controlador no
ASP.NET Core
30/01/2019 • 59 minutes 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, o model binding usaria o valor de id = 5 para definir o parâmetro id como
5 ao invocar essa ação. Consulte Model binding para obter mais detalhes.
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?}");
é um exemplo de roteamento convencional. Nós chamamos esse estilo de roteamento convencional porque
ele estabelece uma convenção para caminhos de URL:
o primeiro segmento do caminho é mapeado para o nome do controlador,
o segundo é mapeado para o nome da ação,
o terceiro segmento é usado para uma id opcional usado para mapear para uma entidade de modelo.
Usando essa rota , o caminho da URL /Products/List é mapeado para a ação
default
ProductsController.List e /Blog/Article/17 é mapeado para BlogController.Article . Esse mapeamento é
baseado somente nos nomes do controlador e da ação e não é baseado em namespaces, locais de arquivos
de origem ou parâmetros de método.
TIP
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.
WARNING
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 pelo model binding 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 .
NOTE
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 .
NOTE
Este exemplo destaca uma diferença importante de programação entre o roteamento de atributo e o roteamento
convencional. O roteamento de atributo requer mais entradas para especificar uma rota; a rota padrão convencional
manipula as rotas de forma mais sucinta. No entanto, o roteamento de atributo permite (e exige) o controle preciso de
quais modelos de rota se aplicam a cada ação.
Com o roteamento de atributo, o nome do controlador e os nomes de ação não desempenham nenhuma
função quanto a qual ação é selecionada. Este exemplo corresponderá as mesmas URLs que o exemplo
anterior.
NOTE
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.
TIP
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.
NOTE
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 foz 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();
}
Roteamento do Razor Pages e do controlador do MVC compartilham uma implementação. Para saber mais
sobre Ordem de Rota, confira os tópicos do Razor Pages disponíveis em Convenções de rota e aplicativo do
Razor Pages: Ordem de Rota.
[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 ]] ).
Usar um transformador de parâmetro para personalizar a substituição de token
A substituição do token pode ser personalizada usando um transformador de parâmetro. Um transformador
de parâmetro implementa IOutboundParameterTransformer e transforma o valor dos parâmetros. Por exemplo,
um transformador de parâmetro SlugifyParameterTransformer personalizado muda o valor de rota
SubscriptionManagement para subscription-management .
// Slugify value
return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
}
}
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()
}
TIP
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;
if (namespc == null)
return;
var template = new StringBuilder();
template.Append(namespc, _baseNamespace.Length + 1,
namespc.Length - _baseNamespace.Length - 1);
template.Replace('.', '/');
template.Append("/[controller]");
NOTE
O que diferencia os dois tipos de sistemas de roteamento é o processo aplicado após uma URL corresponder a um
modelo de rota. No roteamento convencional, os valores de rota da correspondência são usados para escolher a ação e
o controlador em uma tabela de pesquisa com todas as ações roteadas convencionais. No roteamento de atributo,
cada modelo já está associado a uma ação e nenhuma pesquisa adicional é necessária.
Segmentos complexos
Segmentos complexos (por exemplo, [Route("/dog{token}cat")] ), são processados combinando literais da
direita para a esquerda de uma maneira não Greedy. Veja o código-fonte para obter uma descrição. Para
obter mais informações, confira esta edição.
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:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}
result: /UrlGeneration/Destination
Cada parâmetro de rota no modelo de rota tem seu valor substituído por nomes correspondentes com os
valores e os valores de ambiente. Um parâmetro de rota que não tem um valor pode usar um valor padrão se
houver um ou pode ser ignorado se for opcional (como no caso de id neste exemplo). A geração de URL
falhará se qualquer parâmetro de rota obrigatório não tiver um valor correspondente. Se a geração de URL
falhar para uma rota, a rota seguinte será tentada até que todas as rotas tenham sido tentadas ou que uma
correspondência seja encontrada.
O exemplo de Url.Action acima pressupõe que o roteamento seja convencional, mas a geração de URL
funciona de forma semelhante com o roteamento de atributo, embora os conceitos sejam diferentes. Com o
roteamento convencional, os valores de rota são usados para expandir um modelo e os valores de rota para
controller e action normalmente são exibidos no modelo – isso funciona porque as URLs correspondidas
pelo roteamento aderem a uma convenção. No roteamento de atributo, os valores de rota para controller e
action não podem ser exibidos no modelo; em vez disso, eles são usados para pesquisar o modelo a ser
usado.
Este exemplo usa o roteamento de atributo:
// In Startup class
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
using Microsoft.AspNetCore.Mvc;
[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.
NOTE
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 .
WARNING
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;
TIP
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 .
Caso especial para rotas convencionais dedicadas
O roteamento convencional pode usar um tipo especial de definição de rota chamado rota convencional
dedicada. No exemplo a seguir, a rota chamada blog é uma rota convencional dedicada.
app.UseMvc(routes =>
{
routes.MapRoute("blog", "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
Usando essas definições de rota, Url.Action("Index", "Home") gerará o caminho de URL / com a rota
default , mas por quê? Você poderia imaginar que os valores de rota { controller = Home, action = Index }
seriam suficientes para gerar uma URL usando blog e o resultado seria /blog?action=Index&controller=Home .
Rotas convencionais dedicadas dependem de um comportamento especial de valores padrão que não têm
um parâmetro de rota correspondente que impeça que a rota seja "muito ambiciosa" com a geração de URLs.
Nesse caso, os valores padrão são { controller = Blog, action = Article } e nem controller ou action
aparece como um parâmetro de rota. Quando o roteamento executa a geração de URL, os valores fornecidos
devem corresponder aos valores padrão. A geração de URL usando blog falhará porque os valores de
{ controller = Home, action = Index } não correspondem a { controller = Blog, action = Article } . O
roteamento, então, faz o fallback para tentar default , que é bem-sucedido.
Áreas
Áreas são um recurso do MVC usado para organizar funcionalidades relacionadas em um grupo como um
namespace de roteamento (para ações do controlador) e estrutura de pasta (para exibições) separada. O uso
de áreas permite que um aplicativo tenha vários controladores com o mesmo nome, desde que tenham áreas
diferentes. O uso de áreas cria uma hierarquia para fins de roteamento, adicionando outro parâmetro de rota,
area a controller e action . Esta seção aborda como o roteamento interage com as áreas. Consulte Áreas
para obter detalhes sobre como as áreas são usadas com exibições.
O exemplo a seguir configura o MVC para usar a rota convencional padrão e uma rota de área para uma área
chamada Blog :
app.UseMvc(routes =>
{
routes.MapAreaRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
routes.MapRoute("default_route", "{controller}/{action}/{id?}");
});
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.
TIP
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.
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();
}
}
}
NOTE
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.
NOTE
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
NOTE
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.
TIP
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
30/10/2018 • 13 minutes 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 do Model binding 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);
}
WARNING
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 model binding 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 seguinte código usa GetTempFileName , que gerará um IOException se mais de 65.535 arquivos forem
criados sem excluir os arquivos temporários anteriores. Um aplicativo real deve excluir arquivos temporários ou
usar GetTempPath e GetRandomFileName para criar nomes de arquivo temporários. O limite de 65.535 arquivos é
por servidor, então outro aplicativo no servidor poderá usar todos os 65.535 arquivos.
[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:
NOTE
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
NOTE
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o
desempenho.
NOTE
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, o model binding é desabilitado 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 seu próprio model binding.
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 o model binding 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 o model binding é desabilitado, 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 seguinte código usa GetTempFileName , que gerará um IOException se mais de 65.535 arquivos forem
criados sem excluir os arquivos temporários anteriores. Um aplicativo real deve excluir arquivos temporários ou
usar GetTempPath e GetRandomFileName para criar nomes de arquivo temporários. O limite de 65.535 arquivos é
por servidor, então outro aplicativo no servidor poderá usar todos os 65.535 arquivos.
// 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 no
ASP.NET Core
30/10/2018 • 9 minutes 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 :
NOTE
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:
TIP
Confira Testar a lógica do controlador para saber como solicitar dependências explicitamente http://deviq.com/explicit-
dependencies-principle/ em controladores e facilitar o teste do 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 usando um único construtor. Você também
pode substituir o contêiner de 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();
}
namespace ControllerDI.Model
{
public class SampleWebSettings
{
public string Title { get; set; }
public int Updates { get; set; }
}
}
Em seguida, você precisa configurar o aplicativo para usar o modelo de opções e adicionar sua classe de
configuração à coleção de serviços em ConfigureServices :
// 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();
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:
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 controlador de teste, porque não há nenhuma adesão estática ou criação de instância direta
das classes de configuração dentro da classe do controlador.
Injeção de dependência em exibições no ASP.NET
Core
30/10/2018 • 7 minutes 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>
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:
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"};
}
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)
Lógica do controlador de teste no ASP.NET Core
10/11/2018 • 19 minutes to read • Edit Online
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 anterior:
Segue o Princípio de Dependências Explícitas.
Espera DI (injeção de dependência) para fornecer uma instância de IBrainstormSessionRepository .
Pode ser testado com um serviço IBrainstormSessionRepository fictício usando uma estrutura de objeto fictício,
como Moq. Um objeto fictício é um objeto fabricado com um conjunto predeterminado de comportamentos de
propriedade e de método usado para teste. Para saber mais, consulte Introdução aos testes de integração.
O método HTTP GET Index não tem nenhum loop ou branch e chama apenas um método. O teste de unidade para
esta ação:
Imita o serviço IBrainstormSessionRepository usando o método GetTestSessions . GetTestSessions cria duas
sessões de debate fictícias com datas e nomes de sessão.
Executa o método Index .
Faz declarações sobre o resultado retornado pelo método:
Um ViewResult é retornado.
O ViewDataDictionary.Model é um StormSessionViewModel .
Há duas sessões de debate armazenadas no ViewDataDictionary.Model .
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(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());
}
// 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();
}
Quando ModelState não for válido, o mesmo ViewResult será retornado para uma solicitação GET. O teste não
tenta passar um modelo inválido. Passar um modelo inválido não é uma abordagem válida, visto que o model
binding não está em execução (embora um teste de integração use model binding). Nesse caso, o model binding
não está sendo testado. Esses testes de unidade estão testando apenas o código no método de ação.
O segundo teste verifica se, quando o ModelState é válido:
Um novo BrainstormSession é adicionado (por meio do repositório).
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 a validação fictícia no teste. Isso é realizado com a chamada a mockRepo.Verify , que
não será aprovada no teste se o método esperado não tiver sido chamado.
NOTE
A biblioteca do Moq usada neste exemplo possibilita 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.
SessionController no exemplo de aplicativo exibe informações relacionadas a uma sessão de debate específica. O
controlador inclui lógica para lidar com valores id inválidos (há dois cenários return no exemplo a seguir para
abordar esses cenários). A última instrução return retorna um novo StormSessionViewModel para a exibição
(Controllers/SessionController.cs):
return View(viewModel);
}
}
Os testes de unidade incluem um teste para cada cenário return na ação Index do controlador Session:
[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))
.ReturnsAsync((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))
.ReturnsAsync(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);
}
Mudando para o controlador Ideas, o aplicativo expõe a funcionalidade como uma API Web na rota api/ideas :
Uma lista de ideias ( IdeaDTO ) associada com uma sessão de debate é retornada pelo método ForSession .
O método Create adiciona novas ideias a uma sessão.
[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);
}
Evite retornar entidades de domínio de negócios diretamente por meio de chamadas à API. Entidades de domínio:
Geralmente incluem mais dados do que o cliente necessita.
Acople desnecessariamente o modelo de domínio interno do aplicativo à API exposta publicamente.
É possível executar o mapeamento entre entidades de domínio e os tipos retornados ao cliente:
Manualmente com um LINQ Select , como o aplicativo de exemplo usa. Para saber mais, consulte LINQ
(Consulta Integrada à Linguagem).
Automaticamente com uma biblioteca, como AutoMapper.
Em seguida, o aplicativo de exemplo demonstra os testes de unidade para os métodos de API Create e
ForSession do controlador Ideas.
O aplicativo de exemplo contém dois testes ForSession . O primeiro teste determina se ForSession retorna um
NotFoundObjectResult (HTTP não encontrado) para uma sessão inválida:
[Fact]
public async Task ForSession_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSession(testSessionId);
// Assert
var notFoundObjectResult = Assert.IsType<NotFoundObjectResult>(result);
Assert.Equal(testSessionId, notFoundObjectResult.Value);
}
O segundo teste ForSession determina se ForSession retorna uma lista de ideias de sessão ( <List<IdeaDTO>> )
para uma sessão válida. As verificações também examinam a primeira ideia para confirmar se sua propriedade
Name está correta:
[Fact]
public async Task ForSession_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSession(testSessionId);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(okResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}
Para testar o comportamento do método Create quando o ModelState é inválido, o aplicativo de exemplo
adiciona um erro de modelo ao controlador como parte do teste. Não tente testar a validação de modelos ou o
model binding em testes de unidade—teste apenas o comportamento do método de ação quando houver um
ModelState inválido:
[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);
}
O segundo teste de Create depende do repositório retornar null , portanto, o repositório fictício é configurado
para retornar null . Não é necessário criar um banco de dados de teste (na memória ou de outro tipo) e construir
uma consulta que retornará esse resultado. O teste pode ser realizado em uma única instrução, como mostrado no
código de exemplo:
[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((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))
.ReturnsAsync(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);
}
Testar ActionResult<T>
No ASP.NET Core 2.1 ou posterior, o ActionResult<T> (ActionResult<TValue>) permite que você retorne um tipo
derivado de ActionResult ou retorne um tipo específico.
O aplicativo de exemplo inclui um método que retorna um List<IdeaDTO> para uma sessão id determinada. Se a
sessão id não existir, o controlador retornará NotFound:
[HttpGet("forsessionactionresult/{sessionId}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<List<IdeaDTO>>> ForSessionActionResult(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
return result;
}
[Fact]
public async Task ForSessionActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
var nonExistentSessionId = 999;
// Act
var result = await controller.ForSessionActionResult(nonExistentSessionId);
// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
[Fact]
public async Task ForSessionActionResult_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSessionActionResult(testSessionId);
// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(actionResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}
O aplicativo de exemplo também inclui um método para criar um novo Idea para uma determinada sessão. O
controlador retorna:
BadRequest para um modelo inválido.
NotFound se a sessão não existir.
CreatedAtAction quando a sessão for atualizada com a nova ideia.
[HttpPost("createactionresult")]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<ActionResult<BrainstormSession>> CreateActionResult([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (session == null)
{
return NotFound(model.SessionId);
}
await _sessionRepository.UpdateAsync(session);
// Act
var result = await controller.CreateActionResult(model: null);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}
[Fact]
public async Task CreateActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var nonExistentSessionId = 999;
string testName = "test name";
string testDescription = "test description";
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
var createdAtActionResult = Assert.IsType<CreatedAtActionResult>(actionResult.Result);
var returnValue = Assert.IsType<BrainstormSession>(createdAtActionResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnValue.Ideas.Count());
Assert.Equal(testName, returnValue.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnValue.Ideas.LastOrDefault().Description);
}
Recursos adicionais
Testes de integração no ASP.NET Core
Crie e execute testes de unidade com o Visual Studio.
Princípio de Dependências Explícitas
Introdução aos Componentes Razor
04/02/2019 • 13 minutes to read • Edit Online
NOTE
O Razor Components do ASP.NET Core é compatível com ASP.NET Core 3.0 ou posterior.
Blazor é uma estrutura da Web sem suporte e experimental que não deve ser usada para cargas de trabalho de produção no
momento.
Os Componentes Razor do ASP.NET Core executam o lado do servidor no ASP.NET Core, enquanto o Blazor
(Componentes Razor que executam o lado do cliente) é uma estrutura da Web experimental do .NET que usa
C#/Razor e HTML executados no navegador com o WebAssembly. O Blazor fornece todos os benefícios de uma
estrutura de interface do usuário da Web do lado do cliente usando o .NET no cliente.
O desenvolvimento para a Web foi aprimorado de diversas maneiras ao longo dos anos, mas a criação de
aplicativos Web modernos ainda apresenta desafios. O uso do .NET no navegador oferece muitas vantagens que
podem ajudar a tornar o desenvolvimento para Web mais fácil e mais produtivo:
Estabilidade e consistência: o .NET fornece estruturas de programação padronizadas entre plataformas, que
são estáveis, completas e fáceis de usar.
Linguagens modernas inovadoras: as linguagens do .NET estão melhorando constantemente com novos
recursos de linguagem inovadores.
Ferramentas líderes do setor: a família de produtos do Visual Studio fornece uma experiência fantástica de
desenvolvimento no .NET entre plataformas, em Windows, Linux e macOS.
Velocidade e escalabilidade: o .NET tem um forte histórico de desempenho, confiabilidade e segurança para
o desenvolvimento de aplicativos. O uso do .NET como uma solução de pilha completa facilita a criação de
aplicativos rápidos, confiáveis e seguros.
Desenvolvimento de pilha completa que aproveita as habilidades existentes: os desenvolvedores
C#/Razor usam as habilidades em C#/Razor que eles já têm para escrever o código do lado do cliente e
compartilhar a lógica do lado do servidor e do lado do cliente entre os aplicativos.
Amplo suporte a navegadores: os Componentes Razor renderizam a interface do usuário como uma
marcação e um JavaScript comuns. O Blazor é executado no .NET, no navegador, usando padrões abertos da
Web sem nenhum plug-in e nenhuma transpilação de código. O Blazor funciona em todos os navegadores da
Web modernos, incluindo os navegadores móveis.
Modelos de hospedagem
Modelo de hospedagem do lado do servidor
Como os Componentes Razor desvinculam a lógica de renderização de um componente da maneira de aplicar as
atualizações da interface do usuário, há flexibilidade na maneira de hospedar os Componentes Razor. Os
Componentes Razor do ASP.NET Core no .NET Core 3.0 adicionam suporte para a hospedagem dos
Componentes Razor no servidor, em um aplicativo ASP.NET Core, em que todas as atualizações da interface do
usuário são realizadas por uma conexão SignalR. O tempo de execução realiza o envio de eventos da interface do
usuário do navegador para o servidor e, depois de executar os componentes, aplica as atualizações na interface do
usuário retornadas pelo servidor ao navegador. A mesma conexão também é usada para realizar as chamadas de
interoperabilidade do JavaScript.
Para obter mais informações, consulte Razor Components hosting models.
Modelo de hospedagem do lado do cliente
A execução do código do .NET em navegadores da Web é possibilitada por uma tecnologia relativamente nova, o
WebAssembly (abreviado como wasm). O WebAssembly é um padrão aberto da Web compatível com
navegadores da Web sem plug-ins. O WebAssembly é um formato de código de bytes compacto, otimizado para
download rápido e máxima velocidade de execução.
O código do WebAssembly pode acessar a funcionalidade completa do navegador por meio da interoperabilidade
do JavaScript. Ao mesmo tempo, o código do WebAssembly é executado na mesma área restrita confiável que o
JavaScript para impedir ações mal-intencionadas no computador cliente.
Componentes
Os aplicativos são criados com componentes. Um componente é uma parte da interface do usuário, como uma
página, uma caixa de diálogo ou um formulário de entrada de dados. Os componentes podem ser aninhados,
reutilizados e compartilhados entre os projetos.
Um componente é uma classe do .NET. A classe também pode ser escrita diretamente, como uma classe C# (*.cs)
ou, com mais frequência, na forma de uma página de marcação Razor (*.cshtml).
O Razor é uma sintaxe para a combinação da marcação HTML com o código C#. O Razor foi projetado visando a
produtividade do desenvolvedor, permitindo que ele mude entre a marcação e o C# no mesmo arquivo com o
suporte do IntelliSense. A marcação a seguir é um exemplo de um componente básico de caixa de diálogo
personalizada em um arquivo Razor (DialogComponent.cshtml):
<div>
<h2>@Title</h2>
@BodyContent
<button onclick=@OnOK>OK</button>
</div>
@functions {
public string Title { get; set; }
public RenderFragment BodyContent { get; set; }
public Action OnOK { get; set; }
}
Quando esse componente é usado em outro lugar no aplicativo, o IntelliSense acelera o desenvolvimento com o
preenchimento de sintaxe e de parâmetro.
Os componentes podem:
ser aninhados.
ser criados com o Razor (*.cshtml) ou com o código C# (*.cs).
ser compartilhados por meio de bibliotecas de classes.
receber um teste de unidade sem precisar de um navegador DOM.
Infraestrutura
Os Componentes Razor oferecem os principais recursos que a maioria dos aplicativos exige, como:
Layouts
Roteamento
Injeção de dependência
Todos esses recursos são opcionais. Quando um desses recursos não é usado em um aplicativo, a implementação é
eliminada do aplicativo quando ele é publicado pelo Vinculador de IL (linguagem intermediária).
Interoperabilidade do JavaScript
Para aplicativos que exigem bibliotecas JavaScript e APIs do navegador de terceiros, o WebAssembly foi projetado
para interoperar com o JavaScript. Os Componentes Razor são capazes de usar qualquer biblioteca ou API que o
JavaScript possa usar. O código C# pode chamar o código JavaScript, e o código JavaScript pode chamar o código
C#. Para obter mais informações, confira Interoperabilidade do JavaScript.
Otimização
Para aplicativos do lado do cliente, o tamanho do conteúdo é crítico. O Blazor otimiza o tamanho do conteúdo para
reduzir os tempos de download. Por exemplo, as partes não usadas dos assemblies do .NET são removidas durante
o processo de build, as respostas HTTP são compactadas e o tempo de execução do .NET e os assemblies são
armazenados em cache no navegador.
Os Componentes Razor fornecem um tamanho de conteúdo ainda menor do que o Blazor mantendo os
assemblies do .NET, o assembly do aplicativo e o lado do servidor do tempo de execução. Os aplicativos dos
Componentes Razor fornecem somente marcação, scripts e folhas de estilos aos clientes.
Implantação
Use o Blazor para criar um aplicativo do lado do cliente autônomo puro ou um aplicativo do ASP.NET Core de
pilha completa que contenha os aplicativos cliente e os aplicativos para servidor:
Em um aplicativo autônomo do lado do cliente, o aplicativo Blazor é compilado em uma pasta dist que
contém apenas arquivos estáticos. Os arquivos podem ser hospedados no Serviço de Aplicativo do Azure, nas
páginas do GitHub, no IIS (configurado como um servidor de arquivos estático), nos servidores do Node.js e
em vários outros servidores e serviços. O .NET não é necessário no servidor de produção.
Em um aplicativo do ASP.NET Core de pilha completa, o código pode ser compartilhado entre os
aplicativos cliente e os aplicativos para servidor. O aplicativo dos Componentes Razor do ASP.NET Core
resultante, que atende à interface do usuário do lado do cliente e a outros pontos de extremidade de API do lado
do servidor, pode ser criado e implantado em qualquer host de nuvem ou local compatível com o ASP.NET
Core.
Recursos adicionais
WebAssembly
Guia do C#
Razor
HTML
Estado de sessão e aplicativo no ASP.NET Core
30/01/2019 • 34 minutes to read • Edit Online
Gerenciamento de estado
O estado pode ser armazenado usando várias abordagens. Cada abordagem é descrita posteriormente neste
tópico.
Cookies
Cookies armazenam dados entre solicitações. 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 armazenados pelo aplicativo. A maioria dos navegadores restringe o tamanho do cookie a 4.096 bytes.
Somente um número limitado de cookies está disponível para cada domínio.
Uma vez que os cookies estão sujeitos à adulteração, eles devem ser validados pelo aplicativo. Os cookies podem
ser excluídos por usuários e expirarem em clientes. No entanto, os cookies geralmente 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. O usuário é apenas identificado, e não autenticado, na maioria dos casos. O cookie pode armazenar o
nome do usuário, o nome da conta ou a ID de usuário único (como um GUID ). Você pode usar o cookie para
acessar as configurações personalizadas do usuário, como cor preferida da tela de fundo do site.
Esteja ciente do RGPD (Regulamento Geral sobre a Proteção de Dados) da União Europeia ao emitir cookies e
lidar com questões de privacidade. Para obter mais informações, veja Suporte ao RGPD (Regulamento Geral
sobre a Proteção de Dados) no ASP.NET Core.
Estado de sessão
Estado de sessão é um cenário do ASP.NET Core para o armazenamento de dados de usuário enquanto o usuário
procura um aplicativo Web. Estado de sessão usa um armazenamento mantido pelo aplicativo para que os dados
persistam entre solicitações de um cliente. Os dados da sessão são apoiados por um cache e considerados dados
efêmeros—o site deve continuar funcionando sem os dados da sessão. Os dados críticos do aplicativo devem ser
armazenados no banco de dados do usuário e armazenados em cache na sessão apenas como uma otimização de
desempenho.
NOTE
A sessão não é compatível com os aplicativos SignalR porque um Hub SignalR pode ser executado independente de um
contexto HTTP. Por exemplo, isso pode ocorrer quando uma solicitação de sondagem longa é mantida aberta por um hub
além do tempo de vida do contexto HTTP da solicitação.
O ASP.NET Core mantém o estado de sessão fornecendo um cookie para o cliente que contém uma ID de sessão,
que é enviada para o aplicativo com cada solicitação. O aplicativo usa a ID da sessão para buscar os dados da
sessão.
Estado de sessão exibe os seguintes comportamentos:
Uma vez que 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 quando a sessão do navegador termina.
Se um cookie for recebido de uma sessão expirada, será criada uma nova sessão que usa o mesmo cookie de
sessão.
Sessões vazias não são mantidas—a sessão deve ter pelo menos um valor definido nela para que mantenha a
sessão entre solicitações. Quando uma sessão não é mantida, uma nova ID de sessão é gerada para cada nova
solicitação.
O aplicativo mantém uma sessão por um tempo limitado após a última solicitação. O aplicativo define o tempo
limite da sessão ou usa o valor padrão de 20 minutos. Estado de sessão é ideal para armazenar dados de
usuário específicos para uma sessão em particular, mas em que os dados não requerem armazenamento
permanente entre sessões.
Dados da sessão são excluídos quando a implementação ISession.Clear é chamada ou quando a sessão expira.
Não há nenhum mecanismo padrão para informar o código do aplicativo de que um navegador cliente foi
fechado ou quando o cookie de sessão foi excluído ou expirou no cliente. Os modelos de páginas do ASP.NET
Core MVC e Razor incluem suporte para suporte do RGPD (Regulamento Geral sobre a Proteção de Dados) .
Os cookies de estado de sessão não são essenciais, o estado de sessão não funciona quando o
acompanhamento está desabilitado.
WARNING
Não armazene dados confidenciais no estado de sessão. O usuário não pode fechar o navegador e limpar o cookie de
sessão. Alguns navegadores mantêm cookies de sessão válidos entre as janelas do navegador. Uma sessão não pode ser
restringida a um único usuário—o próximo usuário pode continuar a procurar o aplicativo com o mesmo cookie de sessão.
O provedor de cache na memória armazena dados de sessão na memória do servidor em que o aplicativo reside.
Em um cenário de farm de servidores:
Use sessões persistentes para vincular cada sessão a uma instância de aplicativo específico em um servidor
individual. O Serviço de Aplicativo do Azure usa o ARR (Application Request Routing) para impor as sessões
persistentes por padrão. Entretanto, sessões autoadesivas podem afetar a escalabilidade e complicar
atualizações de aplicativos Web. Uma abordagem melhor é usar um Redis ou cache distribuído do SQL Server,
que não requer sessões persistentes. Para obter mais informações, consulte O cache no ASP.NET Core
distribuído.
O cookie de sessão é criptografado por meio de IDataProtector. A Proteção de Dados deve ser configurada
corretamente para ler os cookies de sessão em cada computador. Para obter mais informações, veja Proteção
de dados do ASP.NET Core e Provedores de armazenamento de chaves.
Configurar o estado de sessão
O pacote Microsoft.AspNetCore.Session, incluído no metapacote Microsoft.AspNetCore.App, fornece middleware
para gerenciar o estado de sessão. Para habilitar o middleware da sessão, Startup deve conter:
O pacote Microsoft.AspNetCore.Session fornece middleware para gerenciar o estado de sessão. Para habilitar o
middleware da sessão, Startup deve conter:
Qualquer um dos caches de memória IDistributedCache. A implementação IDistributedCache é usada como
um repositório de backup para a sessão. Para obter mais informações, consulte O cache no ASP.NET Core
distribuído.
Uma chamada para AddSession em ConfigureServices .
Uma chamada para UseSession em Configure .
O código a seguir mostra como configurar o provedor de sessão na memória com uma implementação padrão na
memória de IDistributedCache :
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSession();
app.UseHttpContextItemsMiddleware();
app.UseMvc();
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.CookieHttpOnly = true;
});
services.AddMvc();
}
Opções da sessão
Para substituir os padrões da sessão, use SessionOptions.
OPÇÃO DESCRIÇÃO
OPÇÃO DESCRIÇÃO
A sessão usa um cookie para rastrear e identificar solicitações de um único navegador. Por padrão, esse cookie se
chama .AspNetCore.Session , e usa um caminho de / . Uma vez que 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 HttpOnly usa true como
padrão).
OPÇÃO DESCRIÇÃO
A sessão usa um cookie para rastrear e identificar solicitações de um único navegador. Por padrão, esse cookie se
chama .AspNet.Session , e usa um caminho de / .
Para substituir os padrões de sessão do cookie, use SessionOptions :
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDistributedMemoryCache();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSession(options =>
{
options.Cookie.Name = ".AdventureWorks.Session";
options.IdleTimeout = TimeSpan.FromSeconds(10);
});
}
services.AddSession(options =>
{
options.CookieName = ".AdventureWorks.Session";
options.IdleTimeout = TimeSpan.FromSeconds(10);
});
services.AddMvc();
}
O aplicativo usa a propriedade IdleTimeout para determinar por quanto tempo uma sessão pode ficar ociosa
antes de seu conteúdo no cache do servidor ser abandonado. Essa propriedade é independente da expiração do
cookie. Cada solicitação que passa pelo Middleware da Sessão redefine o tempo limite.
Estado de sessão é sem bloqueio. Se duas solicitações tentarem simultaneamente modificar o conteúdo de uma
sessão, a última solicitação substituirá a primeira. Session é implementado como uma sessão coerente, o que
significa que todo o conteúdo é armazenado junto. Quando duas solicitações buscam modificar valores de sessão
diferentes, a última solicitação pode substituir as alterações de sessão feitas pelo primeira.
Definir e obter valores de Session
Estado de sessão é acessado de uma classe PageModel Razor Pages ou classe Controlador MVC com
HttpContext.Session. Esta propriedade é uma implementação de ISession.
A implementação de ISession fornece vários métodos de extensão para definir e recuperar valores de inteiro e
cadeia de caracteres. Os métodos de extensão estão no namespace Microsoft.AspNetCore.Http (adicione uma
instrução using Microsoft.AspNetCore.Http; para acessar os métodos de extensão) quando o pacote
Microsoft.AspNetCore.Http.Extensions for referenciado pelo projeto. Ambos os pacotes são incluídos no
metapacote Microsoft.AspNetCore.App.
A implementação de ISession fornece vários métodos de extensão para definir e recuperar valores de inteiro e
cadeia de caracteres. Os métodos de extensão estão no namespace Microsoft.AspNetCore.Http (adicione uma
instrução using Microsoft.AspNetCore.Http; para acessar os métodos de extensão) quando o pacote
Microsoft.AspNetCore.Http.Extensions for referenciado pelo projeto.
Métodos de extensão ISession :
Get(ISession, String)
GetInt32(ISession, String)
GetString(ISession, String)
SetInt32(ISession, String, Int32)
SetString(ISession, String, String)
O exemplo a seguir recupera o valor da sessão para a chave IndexModel.SessionKeyName ( _Name no aplicativo de
exemplo) em uma página do Razor Pages:
@page
@using Microsoft.AspNetCore.Http
@model IndexModel
...
Name: @HttpContext.Session.GetString(IndexModel.SessionKeyName)
O exemplo a seguir mostra como definir e obter um número inteiro e uma cadeia de caracteres:
return RedirectToAction("SessionNameYears");
}
Todos os dados de sessão devem ser serializados para habilitar um cenário de cache distribuído, mesmo ao usar o
cache na memória. Cadeia de caracteres mínima e serializadores número são fornecidos (confira os métodos e os
métodos de extensão de ISession). Tipos complexos devem ser serializados pelo usuário por meio de outro
mecanismo, como JSON.
Adicione os seguintes métodos de extensão para definir e obter objetos serializáveis:
O exemplo a seguir mostra como definir e obter um objeto serializável com os métodos de extensão:
// Requires you add the Set and Get extension method mentioned in the topic.
if (HttpContext.Session.Get<DateTime>(SessionKeyTime) == default(DateTime))
{
HttpContext.Session.Set<DateTime>(SessionKeyTime, currentTime);
}
return RedirectToAction("GetDate");
}
TempData
O ASP.NET Core expõe a propriedade TempData de um modelo de página do Razor Pages ou TempData de um
controlador MVC. 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 são necessários dados para mais de uma única solicitação. TempData é implementado por provedores de
TempData usando cookies ou estado de sessão.
Provedores de TempData
No ASP.NET Core 2.0 ou posterior, o provedor TempData baseado em cookies é usado por padrão para
armazenar TempData em cookies.
Os dados do cookie são criptografados usando IDataProtector, codificado com Base64UrlTextEncoder, em
seguida, em partes. Como o cookie é 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.
No ASP.NET Core 1.0 e 1.1, o provedor de TempData do estado de sessão é o provedor padrão.
Escolha um provedor de TempData
Escolher um provedor de TempData envolve várias considerações, como:
1. O aplicativo já usa estado de sessão? Se for o caso, usar o provedor de TempData do estado de sessão não terá
custos adicionais para o aplicativo (além do tamanho dos dados).
2. O aplicativo usa TempData apenas raramente para quantidades relativamente pequenas de dados (até 500
bytes)? Em caso afirmativo, o provedor de TempData do cookie adiciona 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 farm de servidores em vários servidores? Se for o caso, não será necessária
nenhuma configuração adicional para usar o provedor TempData do cookie fora da Proteção de Dados
(confira Proteção de dados do ASP.NET Core e Provedores de armazenamento de chaves).
NOTE
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. Ao usar o provedor TempData do cookie, verifique se o aplicativo não ultrapassará esses
limites. Considere o tamanho total dos dados. Conta para aumento no tamanho de cookie devido à criptografia e ao
agrupamento.
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddSessionStateTempDataProvider();
services.AddSession();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSession();
app.UseMvc();
}
IMPORTANT
Se o alvo for o .NET Framework e o provedor TempData baseado em sessão for usado, adicione o pacote
Microsoft.AspNetCore.Session ao projeto.
Cadeias de consulta
É possível passar uma quantidade limitada de dados de uma solicitação para outra adicionando-a à 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. Uma vez que cadeias
de consulta de URL são públicas, nunca use cadeias de consulta para dados confidenciais.
Além da possibilidade de ocorrência de compartilhamento não intencional, 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 então podem roubar dados
do usuário do aplicativo ou executar ações mal-intencionadas em nome do usuário. Qualquer estado de sessão ou
aplicativo preservado deve se proteger contra ataques CSRF. Para obter mais informações, confira Impedir
ataques de XSRF/CSRF (solicitação intersite forjada).
Campos ocultos
Dados podem ser salvos em campos de formulário ocultos e postados novamente na solicitação seguinte. Isso é
comum em formulários com várias páginas. Uma vez que o cliente potencialmente pode adulterar os dados, o
aplicativo deve sempre revalidar os dados armazenados nos campos ocultos.
HttpContext.Items
A coleção HttpContext.Items é usada para armazenar dados durante o processamento de uma única solicitação. O
conteúdo da coleção é descartado após uma solicitação ser processada. A coleção Items costuma ser usada para
permitir que componentes ou middleware se comuniquem quando operam em diferentes momentos durante
uma solicitação e não têm nenhuma maneira direta de passar parâmetros.
No exemplo a seguir, middleware adiciona isVerified à coleção Items .
app.Use(async (context, next) =>
{
// perform some verification
context.Items["isVerified"] = true;
await next.Invoke();
});
Para middleware usado apenas por um único aplicativo, chaves string são aceitáveis. Middleware
compartilhado entre instâncias do aplicativo deve usar chaves de objeto únicas para evitar colisões de chaves. O
exemplo a seguir mostra como usar uma chave de objeto exclusiva definida em uma classe de middleware:
await _next(httpContext);
}
}
await _next(httpContext);
}
}
Outros códigos podem acessar o valor armazenado em HttpContext.Items usando a chave exposta pela classe do
middleware:
HttpContext.Items
.TryGetValue(HttpContextItemsMiddleware.HttpContextItemsMiddlewareKey,
out var middlewareSetValue);
SessionInfo_MiddlewareValue =
middlewareSetValue?.ToString() ?? "Middleware value not set!";
Essa abordagem também tem a vantagem de eliminar o uso de cadeias de caracteres de chave no código.
Cache
O cache é uma maneira eficiente de armazenar e recuperar dados. O aplicativo pode controlar o tempo de vida de
itens em cache.
Dados armazenados em cache não são associados uma solicitação, usuário ou sessão específico. Tenha cuidado
para não armazenar em cache dados específicos do usuário que podem ser recuperados pelas
solicitações de outros usuários.
Para obter mais informações, consulte Cache de resposta no ASP.NET Core.
Injeção de dependência
Use a Injeção de dependência para disponibilizar dados para todos os usuários:
1. Defina um serviço que contenha os dados. Por exemplo, uma classe denominada MyAppData está definida:
Erros comuns
"Não é possível resolver o serviço para o tipo 'Microsoft.Extensions.Caching.Distributed.IDistributedCache'
ao tentar ativar 'Microsoft.AspNetCore.Session.DistributedSessionStore'."
Geralmente, isso é causado quando não é configurada pelo menos uma implementação de
IDistributedCache . Para obter mais informações, consulte O cache no ASP.NET Core distribuído e
Memória de cache no ASP.NET Core.
Caso a sessão de middleware não consiga manter uma sessão (por exemplo, se o repositório de backup
não estiver disponível), o middleware registrará a exceção e a solicitação continuará normalmente. Isso leva
a um comportamento imprevisível.
Por exemplo, um usuário armazena um carrinho de compras na sessão. O usuário adiciona um item ao
carrinho, mas a confirmação falha. O aplicativo não sabe sobre a falha, assim, relata ao usuário que o item
foi adicionado ao seu carrinho, o que não é verdade.
A abordagem recomendada para verificar se há erros é chamar await feature.Session.CommitAsync(); do
código de aplicativo quando o aplicativo tiver terminado de gravar na sessão. CommitAsync gerará uma
exceção se o repositório de backup não estiver disponível. Se CommitAsync falhar, o aplicativo poderá
processar a exceção. LoadAsync gera sob as mesmas condições em que o armazenamento de dados não
está disponível.
Recursos adicionais
Hospedar o ASP.NET Core em um web farm
Auxiliares de Marca no ASP.NET Core
29/10/2018 • 25 minutes to read • Edit Online
<label asp-for="Movie.Title"></label>
<label for="Movie_Title">Title</label>
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers
@using AuthoringTagHelpers
@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> .
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:
@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.
Recursos adicionais
Auxiliares de marca de autor
Trabalhando com formulários
TagHelperSamples no GitHub contém amostras de Auxiliar de Marca para trabalhar com o
Bootstrap.
Auxiliares de marca de autor no ASP.NET Core
04/02/2019 • 27 minutes to read • Edit Online
<email>Support</email>
O servidor usará nosso auxiliar de marca de email para converter essa marcação como a seguir:
<a href="mailto:[email protected]">[email protected]</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
}
}
}
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 e, 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 Processsubstituí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-html]
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 auxiliar de marca para uma exibição usando um FQN, primeiro adicione o FQN (
AuthoringTagHelpers.TagHelpers.EmailTagHelper ) e, em seguida, o nome do assembly ( AuthoringTagHelpers,
não necessariamente o namespace ). 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.
1. 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>
2. 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.
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:
Nomes de classe e de propriedade na formatação Pascal Case para auxiliares de marcações são
convertidos em Kebab Case. Portanto, para usar o atributo MailTo , você usará o equivalente de
<email mail-to="value"/> .
A última linha define o conteúdo concluído para nosso tag helper minimamente funcional.
A linha realçada mostra a sintaxe para adicionar atributos:
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a"; // Replaces <email> with <a> tag
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-html]
2. Execute o aplicativo e verifique se ele gera os links corretos.
NOTE
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>");
}
}
}
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> .
[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.
Também use o [HtmlTargetElement] para alterar o nome do elemento de destino. Por exemplo, se você deseja
que o BoldTagHelper seja direcionado a marcações <MyBold> , use o seguinte atributo:
[HtmlTargetElement("MyBold")]
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; }
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ção <website-information /> em Kebab Case 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";
WebsiteContext webContext = new WebsiteContext {
Version = new Version(1, 3),
CopyrightYear = 1638,
Approved = true,
TagsToShow = 131 };
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
NOTE
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.
NOTE
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.
NOTE
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 ).
[HtmlTargetElement("p")]
public class AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}
NOTE
A classe AutoLinkerHttpTagHelper é direcionada a elementos p e usa o Regex para criar a âncora.
<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 AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}
[HtmlTargetElement("p")]
public class AutoLinkerWwwTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(www\.)(\S+)\b",
"<a target=\"_blank\" href=\"http://$0\">$0</a>")); // www version
}
}
}
5. Execute o aplicativo. Observe que o texto www é renderizado como um link, ao contrário do texto HTTP.
Se você colocar um ponto de interrupção em ambas as classes, poderá ver que a classe do auxiliar de
marca HTTP é executada primeiro. O problema é que a saída do auxiliar de marca é armazenada em cache
e quando o auxiliar de marca WWW é executado, ele substitui a saída armazenada em cache do auxiliar de
marca HTTP. Mais adiante no tutorial, veremos como controlar a ordem na qual os auxiliares de marca
são executados. Corrigiremos o código com o seguinte:
public class AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = output.Content.IsModified ? output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}
[HtmlTargetElement("p")]
public class AutoLinkerWwwTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = output.Content.IsModified ? output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(www\.)(\S+)\b",
"<a target=\"_blank\" href=\"http://$0\">$0</a>")); // www version
}
}
NOTE
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 garante que o auxiliar de marca HTTP seja executado antes do auxiliar de marca 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.
Auxiliares de marca em formulários no ASP.NET Core
14/01/2019 • 28 minutes to read • Edit Online
Amostra:
O tempo de execução do MVC gera o valor do atributo action dos atributos asp-controller e asp-action do
Auxiliar de marca de formulário. O Auxiliar de marca de formulário também gera um Token de verificação de
solicitação oculto para evitar a falsificação de solicitações entre sites (quando usado com o atributo
[ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger um Formulário HTML puro contra
falsificação de solicitações entre sites e o Auxiliar de marca de formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o atributo HTML action . Um
aplicativo com uma rota chamada register poderia usar a seguinte marcação para a página de registro:
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:
NOTE
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-local"
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"
[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
As anotações de dados aplicadas às propriedades Email e Password geram metadados no modelo. O Auxiliar de
marca de entrada consome os metadados do modelo e produz atributos HTML5 data-val-* (consulte Validação
de modelo). Esses atributos descrevem os validadores a serem anexados aos campos de entrada. Isso fornece
validação de jQuery e HTML5 discreto. Os atributos discretos têm o formato data-val-rule="Error Message" , em
que a regra é o nome da regra de validação (como data-val-required , data-val-email , data-val-maxlength etc.) Se
uma mensagem de erro for fornecida no atributo, ela será exibida como o valor para do atributo data-val-rule .
Também há atributos do formulário data-val-ruleName-argumentName="argumentValue" que fornecem detalhes
adicionais sobre a regra, por exemplo, data-val-maxlength-max="1024" .
Alternativas de Auxiliar HTML ao Auxiliar de marca de entrada
Html.TextBox , Html.TextBoxFor , Html.Editor e Html.EditorFor têm recursos que se sobrepõem aos di Auxiliar de
marca de entrada. O Auxiliar de marca de entrada define automaticamente o atributo type ; Html.TextBox e
Html.TextBoxFor não o fazem. Html.Editor e Html.EditorFor manipulam coleções, objetos complexos e modelos;
o Auxiliar de marca de entrada não o faz. O Auxiliar de marca de entrada, Html.EditorFor e Html.TextBoxFor são
fortemente tipados (eles usam expressões lambda); Html.TextBox e Html.Editor não usam (eles usam nomes de
expressão).
HtmlAttributes
@Html.Editor() e @Html.EditorFor() usam uma entrada ViewDataDictionary especial 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. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
Gera o seguinte:
[DataType(DataType.Password)]
public string Password { get; set; }
O método de ação:
@model Person
@{
var index = (int)ViewData["index"];
}
O modelo Views/Shared/EditorTemplates/String.cshtml:
@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>
*@
foreach deve ser usado, se possível, quando o valor está prestes a ser usado em um contexto equivalente asp-for
ou Html.DisplayFor . Em geral, for é melhor do que foreach (se o cenário permitir) porque não é necessário
alocar um enumerador; no entanto, avaliar um indexador em uma expressão LINQ pode ser caro, o que deve ser
minimizado.
NOTE
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
O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID associada ao elemento
<input> . Auxiliares de marca geram elementos id e for consistentes para que eles possam ser associados
corretamente. A legenda neste exemplo é proveniente do atributo Display . Se o modelo não contivesse um
atributo Display , a legenda seria o nome da propriedade da expressão.
O Validation Message Tag Helper é usado com o atributo asp-validation-for em um elemento HTML span.
<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.
NOTE
É 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> .
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
O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o elemento select e asp-items
especifica os elementos option. Por exemplo:
Amostra:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
O método Index inicializa o CountryViewModel , define o país selecionado e o transmite para a exibição Index .
[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
NOTE
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um modelo de exibição é mais
robusto para fornecer metadados MVC e, geralmente, menos problemático.
O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os outros atributos do Auxiliar de
marca requerem (como asp-items )
Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os elementos SelectListItem dos
valores enum .
Amostra:
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
}
}
É 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 .
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; }
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
O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma seleção. Por exemplo, o
seguinte método de ação e exibição gerarão HTML semelhante ao código acima:
O elemento <option> correto será selecionado (contém o atributo selected="selected" ) dependendo do valor
atual de Country .
Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Elemento de formulário HTML
Token de verificação de solicitação
Model binding no ASP.NET Core
Validação de modelo no ASP.NET Core MVC
Interface IAttributeAdapter
Snippets de código para este documento
Componentes do Auxiliar de Marca no ASP.NET Core
30/10/2018 • 10 minutes to read • Edit Online
Casos de uso
Dois casos de uso comum dos Componentes do Auxiliar de Marca incluem:
1. Injetar um <link> no <head> .
2. Injetar um <script> no <body> .
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace RazorPagesSample.TagHelpers
{
public class AddressStyleTagHelperComponent : TagHelperComponent
{
private readonly string _style =
@"<link rel=""stylesheet"" href=""/css/address.css"" />";
return Task.CompletedTask;
}
}
}
No código anterior:
AddressStyleTagHelperComponent implementa TagHelperComponent. A abstração:
Permite a inicialização da classe com um TagHelperContext.
Habilita o uso de Componentes do Auxiliar de Marca para adicionar ou modificar elementos HTML.
A propriedade Order define a ordem na qual os Componentes são renderizados. Order é necessário quando há
vários usos de Componentes do Auxiliar de Marca em um aplicativo.
ProcessAsync compara o valor da propriedade TagName do contexto de execução com head . Se a comparação
for avaliada como verdadeira, o conteúdo do campo _style será injetado no elemento HTML <head> .
Injetar no elemento HTML body
O Componente do Auxiliar de Marca body pode injetar um elemento <script> no elemento <body> . O código a
seguir demonstra essa técnica:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace RazorPagesSample.TagHelpers
{
public class AddressScriptTagHelperComponent : TagHelperComponent
{
public override int Order => 2;
Um arquivo HTML separado é usado para armazenar o elemento <script> . O arquivo HTML torna o código mais
limpo e mais sustentável. O código anterior lê o conteúdo de TagHelpers/Templates/AddressToolTipScript.html e
acrescenta-o com a saída do Auxiliar de Marca. O arquivo AddressToolTipScript.html inclui a seguinte marcação:
<script>
$("address[printable]").hover(function() {
$(this).attr({
"data-toggle": "tooltip",
"data-placement": "right",
"title": "Home of Microsoft!"
});
});
</script>
O código anterior associa um widget de Dica de ferramenta de inicialização a qualquer elemento <address> que
inclua um atributo printable . O efeito é visível quando um ponteiro do mouse focaliza o elemento.
Registrar um componente
Um Componente do Auxiliar de Marca precisa ser adicionado à coleção de Componentes do Auxiliar de Marca do
aplicativo. Há três maneiras de adicioná-lo à coleção:
1. Registro por contêiner de serviços
2. Registro por arquivo Razor
3. Registro por Modelo de página ou controlador
Registro por contêiner de serviços
Se a classe do Componente do Auxiliar de Marca não for gerenciada com ITagHelperComponentManager, ela
precisará ser registrada com o sistema de DI (injeção de dependência). O código Startup.ConfigureServices a
seguir registra as classes AddressStyleTagHelperComponent e AddressScriptTagHelperComponent com um tempo de
vida transitório:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddTransient<ITagHelperComponent,
AddressScriptTagHelperComponent>();
services.AddTransient<ITagHelperComponent,
AddressStyleTagHelperComponent>();
}
@using RazorPagesSample.TagHelpers;
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
@inject ITagHelperComponentManager manager;
@{
string markup;
if (Model.IsWeekend)
{
markup = "<em class='text-warning'>Office closed today!</em>";
}
else
{
markup = "<em class='text-info'>Office open today!</em>";
}
No código anterior:
A diretiva @inject fornece uma instância de ITagHelperComponentManager . A instância é atribuída a uma variável
chamada manager para acesso downstream no arquivo Razor.
Uma instância do AddressTagHelperComponent é adicionada à coleção de Componentes do Auxiliar de Marca do
aplicativo.
AddressTagHelperComponent é modificado para acomodar um construtor que aceita os parâmetros markup e order :
if (IsWeekend)
{
markup = "<em class='text-warning'>Office closed today!</em>";
}
else
{
markup = "<em class='text-info'>Office open today!</em>";
}
_tagHelperComponentManager.Components.Add(
new AddressTagHelperComponent(markup, 1));
}
}
No código anterior:
A injeção do construtor é usada para acessar uma instância de ITagHelperComponentManager .
Uma instância do AddressTagHelperComponent é adicionada à coleção de Componentes do Auxiliar de Marca do
aplicativo.
Criar um componente
Para criar um Componente do Auxiliar de Marca personalizado:
Crie uma classe pública derivada de TagHelperComponentTagHelper.
Aplique um atributo [HtmlTargetElement] à classe. Especifique o nome do elemento HTML de destino.
Opcional: aplique um atributo [EditorBrowsable(EditorBrowsableState.Never)] à classe para suprimir a exibição
do tipo no IntelliSense.
O código a seguir cria um Componente do Auxiliar de Marca personalizado que tem como destino o elemento
HTML <address> :
using System.ComponentModel;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
namespace RazorPagesSample.TagHelpers
{
[HtmlTargetElement("address")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class AddressTagHelperComponentTagHelper : TagHelperComponentTagHelper
{
public AddressTagHelperComponentTagHelper(
ITagHelperComponentManager componentManager,
ILoggerFactory loggerFactory) : base(componentManager, loggerFactory)
{
}
}
}
Usar o Componente do Auxiliar de Marca address personalizado para inserir uma marcação HTML da seguinte
maneira:
O método ProcessAsync anterior injeta o HTML fornecido para SetHtmlContent no elemento <address>
correspondente. A injeção ocorre quando:
O valor da propriedade TagName do contexto de execução é igual a address .
O elemento <address> correspondente tem um atributo printable .
Por exemplo, a instrução if é avaliada como verdadeira ao processar o seguinte elemento <address> :
<address printable>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
Recursos adicionais
Injeção de dependência no ASP.NET Core
Injeção de dependência em exibições no ASP.NET Core
Auxiliares de marcação internos do ASP.NET Core
Auxiliar de Marca de Âncora no ASP.NET Core
10/01/2019 • 12 minutes 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);
}
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 asp-area causa um remapeamento das rotas.
Uso no Razor Pages
As áreas do Razor Pages têm suporte no ASP.NET Core 2.1 ou posterior.
Considere a seguinte hierarquia de diretórios:
{Nome do projeto}
wwwroot
Áreas
Sessões
Páginas
_ViewStart.cshtml
Index.cshtml
Index.cshtml.cs
Páginas
A marcação para fazer referência ao Razor Page de Índice da área Sessões é:
<a asp-area="Sessions"
asp-page="/Index">View Sessions</a>
O HTML gerado:
services.AddMvc()
.AddRazorPagesOptions(options => options.AllowAreas = true);
Uso no MVC
Considere a seguinte hierarquia de diretórios:
{Nome do projeto}
wwwroot
Áreas
Blogs
Controladores
HomeController.cs
Exibições
Início
AboutBlog.cshtml
Index.cshtml
_ViewStart.cshtml
Controladores
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. A marcação para fazer referência à exibição AboutBlog é:
<a asp-area="Blogs"
asp-controller="Home"
asp-action="AboutBlog">About Blog</a>
O HTML gerado:
app.UseMvc(routes =>
{
// need route and attribute on controller: [Area("Blogs")]
routes.MapRoute(name: "mvcAreaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}");
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. O Auxiliar de Marca de Âncora usa o domínio público do site ao gerar a
URL.
asp-host
O atributo asp-host é para especificar um nome do host na sua URL. Por exemplo:
<a asp-protocol="https"
asp-host="microsoft.com"
asp-controller="Home"
asp-action="About">About</a>
O HTML gerado:
<a href="https://microsoft.com/Home/About">About</a>
asp-page
O atributo asp-page é usado com as Páginas Razor. Use-o para definir um valor de atributo href da marca de
âncora para uma página específica. Prefixar o nome de página com uma barra ("/") cria a URL.
O exemplo a seguir aponta para o participante da Página Razor:
<a asp-page="/Attendee"
asp-route-attendeeid="10">View Attendee</a>
O HTML gerado:
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 . Quando o método é assíncrono, o sufixo Async também é omitido.
<a asp-page="/Attendee"
asp-page-handler="Profile"
asp-route-attendeeid="12">Attendee Profile</a>
O HTML gerado:
Recursos adicionais
Áreas no ASP.NET Core
Introdução a Páginas do Razor no ASP.NET Core
Versão de compatibilidade do ASP.NET Core MVC
Auxiliar de Marca de Cache no ASP.NET Core MVC
10/11/2018 • 8 minutes to read • Edit Online
<cache>@DateTime.Now</cache>
A primeira solicitação para a página que contém o Auxiliar de Marca exibe a data atual. Solicitações adicionais
mostram o valor armazenado em cache até que o cache expire (padrão de 20 minutos) ou até que a data em
cache seja removida do cache.
enabled determina se o conteúdo envolto pelo Auxiliar de Marca de Cache é armazenado em cache. O padrão é
true . Se definido como false , a saída renderizada não é armazenada em cache.
Exemplo:
<cache enabled="true">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
expires-on
TIPO DE ATRIBUTO EXEMPLO
expires-after
TIPO DE ATRIBUTO EXEMPLO PADRÃO
expires-after 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>
O Mecanismo de Exibição do Razor define o valor expires-after padrão como vinte minutos.
expires-sliding
TIPO DE ATRIBUTO EXEMPLO
TimeSpan @TimeSpan.FromSeconds(60)
Define a hora em que uma entrada de cache deverá ser removida se o seu valor não tiver sido acessado.
Exemplo:
<cache expires-sliding="@TimeSpan.FromSeconds(60)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-header
TIPO DE ATRIBUTO EXEMPLOS
vary-by-header aceita uma lista delimitada por vírgulas de valores de cabeçalho que disparam uma atualização
do cache quando eles mudam.
O exemplo a seguir monitora o valor do cabeçalho User-Agent . O exemplo armazena em cache o conteúdo para
cada User-Agent diferente apresentado ao servidor Web:
<cache vary-by-header="User-Agent">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-query
TIPO DE ATRIBUTO EXEMPLOS
vary-by-query aceita uma lista delimitada por vírgula de Keys em uma cadeia de consulta (Query) que dispara
uma atualização do cache quando o valor de qualquer chave listada é alterado.
O exemplo a seguir monitora os valores de Make e Model . O exemplo armazena em cache o conteúdo para
todos os diferentes Make e Model apresentados ao servidor Web:
<cache vary-by-query="Make,Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-route
TIPO DE ATRIBUTO EXEMPLOS
vary-by-route aceita uma lista delimitada por vírgulas de nomes de parâmetros de rota que disparam uma
atualização do cache quando o valor de parâmetro de dados de rota muda.
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 EXEMPLOS
vary-by-cookie aceita uma lista delimitada por vírgulas de nomes de cookie que disparam uma atualização do
cache quando os valores de cookie mudam.
O exemplo a seguir monitora o cookie associado à identidade do ASP.NET Core. Quando um usuário é
autenticado, uma alteração ao cookie de Identidade dispara uma atualização do cache:
<cache vary-by-cookie=".AspNetCore.Identity.Application">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-user
TIPO DE ATRIBUTO EXEMPLOS PADRÃO
vary-by-user especifica se o cache é redefinido ou não quando o usuário conectado (ou a Entidade de Contexto)
muda. 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 monitora o usuário conectado atual para disparar uma atualização de cache:
<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 entrada e saída. Quando o valor é definido
como true , um ciclo de autenticação invalida o cache para o usuário autenticado. O cache é invalidado porque
um novo valor de cookie exclusivo é gerado quando um usuário é autenticado. O cache é mantido para o estado
anônimo quando nenhum cookie está presente ou quando o cookie expirou. Se o usuário não estiver
autenticado, o cache será mantido.
vary-by
TIPO DE ATRIBUTO EXEMPLO
vary-by permite a personalização de quais dados são armazenados em 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 resulta em um cenário em 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 a soma 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.
Ação:
Index.cshtml:
<cache vary-by="@Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
priority
TIPO DE ATRIBUTO EXEMPLOS PADRÃO
priority fornece diretrizes de remoção do cache para o provedor de cache interno. O servidor Web remove
entradas de cache Low primeiro quando está 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 os itens armazenados em cache sempre
sejam retidos. Veja os tópicos na seção 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
adicionará o serviço se ele não tiver sido adicionado.
Recursos adicionais
Memória de cache no ASP.NET Core
Introdução à identidade do ASP.NET Core
Auxiliar de Marca de Cache Distribuído no ASP.NET
Core
29/10/2018 • 3 minutes to read • Edit Online
O Auxiliar de Marca de Cache Distribuído herda da mesma classe que o Auxiliar de Marca de Cache. Para obter
descrições desses atributos, confira Auxiliar de Marca de Cache.
name
TIPO DE ATRIBUTO EXEMPLO
name é obrigatório. O atributo name é usado como uma chave para cada instância de cache armazenada. Ao
contrário do Auxiliar de Marca de Cache que atribui uma chave de cache a cada instância com base no nome da
página do Razor e na localização na página do Razor, o Auxiliar de Marca de Cache Distribuído somente localiza
suas chaves no atributo name .
Exemplo:
<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
O cache no ASP.NET Core distribuído
Memória de cache no ASP.NET Core
Introdução à identidade do ASP.NET Core
Auxiliar de Marca de Ambiente no ASP.NET Core
29/10/2018 • 2 minutes 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>
exclude
Em contraste com o atributo include , o conteúdo da marcação <environment> é processado quando o ambiente
de hospedagem não corresponde a um ambiente listado no valor do atributo exclude .
<environment exclude="Development">
<strong>HostingEnvironment.EnvironmentName is not Development</strong>
</environment>
Recursos adicionais
Usar vários ambientes no ASP.NET Core
Auxiliares de marca em formulários no ASP.NET Core
14/01/2019 • 28 minutes to read • Edit Online
Amostra:
O tempo de execução do MVC gera o valor do atributo action dos atributos asp-controller e asp-action do
Auxiliar de marca de formulário. O Auxiliar de marca de formulário também gera um Token de verificação de
solicitação oculto para evitar a falsificação de solicitações entre sites (quando usado com o atributo
[ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger um Formulário HTML puro contra
falsificação de solicitações entre sites e o Auxiliar de marca de formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o atributo HTML action . Um
aplicativo com uma rota chamada register poderia usar a seguinte marcação para a página de registro:
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:
NOTE
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-local"
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"
[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
As anotações de dados aplicadas às propriedades Email e Password geram metadados no modelo. O Auxiliar de
marca de entrada consome os metadados do modelo e produz atributos HTML5 data-val-* (consulte Validação
de modelo). Esses atributos descrevem os validadores a serem anexados aos campos de entrada. Isso fornece
validação de jQuery e HTML5 discreto. Os atributos discretos têm o formato data-val-rule="Error Message" , em
que a regra é o nome da regra de validação (como data-val-required , data-val-email , data-val-maxlength etc.) Se
uma mensagem de erro for fornecida no atributo, ela será exibida como o valor para do atributo data-val-rule .
Também há atributos do formulário data-val-ruleName-argumentName="argumentValue" que fornecem detalhes
adicionais sobre a regra, por exemplo, data-val-maxlength-max="1024" .
Alternativas de Auxiliar HTML ao Auxiliar de marca de entrada
Html.TextBox , Html.TextBoxFor , Html.Editor e Html.EditorFor têm recursos que se sobrepõem aos di Auxiliar de
marca de entrada. O Auxiliar de marca de entrada define automaticamente o atributo type ; Html.TextBox e
Html.TextBoxFor não o fazem. Html.Editor e Html.EditorFor manipulam coleções, objetos complexos e modelos;
o Auxiliar de marca de entrada não o faz. O Auxiliar de marca de entrada, Html.EditorFor e Html.TextBoxFor são
fortemente tipados (eles usam expressões lambda); Html.TextBox e Html.Editor não usam (eles usam nomes de
expressão).
HtmlAttributes
@Html.Editor() e @Html.EditorFor() usam uma entrada ViewDataDictionary especial 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. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
Gera o seguinte:
[DataType(DataType.Password)]
public string Password { get; set; }
O método de ação:
@model Person
@{
var index = (int)ViewData["index"];
}
O modelo Views/Shared/EditorTemplates/String.cshtml:
@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>
*@
foreach deve ser usado, se possível, quando o valor está prestes a ser usado em um contexto equivalente asp-for
ou Html.DisplayFor . Em geral, for é melhor do que foreach (se o cenário permitir) porque não é necessário
alocar um enumerador; no entanto, avaliar um indexador em uma expressão LINQ pode ser caro, o que deve ser
minimizado.
NOTE
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
O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID associada ao elemento
<input> . Auxiliares de marca geram elementos id e for consistentes para que eles possam ser associados
corretamente. A legenda neste exemplo é proveniente do atributo Display . Se o modelo não contivesse um
atributo Display , a legenda seria o nome da propriedade da expressão.
O Validation Message Tag Helper é usado com o atributo asp-validation-for em um elemento HTML span.
<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.
NOTE
É 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> .
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
O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o elemento select e asp-items
especifica os elementos option. Por exemplo:
Amostra:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
O método Index inicializa o CountryViewModel , define o país selecionado e o transmite para a exibição Index .
[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
NOTE
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um modelo de exibição é mais
robusto para fornecer metadados MVC e, geralmente, menos problemático.
O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os outros atributos do Auxiliar de
marca requerem (como asp-items )
Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os elementos SelectListItem dos
valores enum .
Amostra:
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
}
}
É 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 .
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; }
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
O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma seleção. Por exemplo, o
seguinte método de ação e exibição gerarão HTML semelhante ao código acima:
O elemento <option> correto será selecionado (contém o atributo selected="selected" ) dependendo do valor
atual de Country .
Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Elemento de formulário HTML
Token de verificação de solicitação
Model binding no ASP.NET Core
Validação de modelo no ASP.NET Core MVC
Interface IAttributeAdapter
Snippets de código para este documento
Auxiliar de Marca de Imagem no ASP.NET Core
29/10/2018 • 3 minutes to read • Edit Online
Se o arquivo estático existe no diretório /wwwroot/images/, o HTML gerado é semelhante ao seguinte (o hash será
diferente):
O valor atribuído ao parâmetro v é o valor de hash do arquivo asplogo.png em disco. Se o servidor Web não
conseguir obter acesso de leitura ao arquivo estático, nenhum parâmetro v será adicionado ao atributo src na
marcação renderizada.
Recursos adicionais
Memória de cache no ASP.NET Core
Auxiliares de marca em formulários no ASP.NET Core
14/01/2019 • 28 minutes to read • Edit Online
Amostra:
O tempo de execução do MVC gera o valor do atributo action dos atributos asp-controller e asp-action do
Auxiliar de marca de formulário. O Auxiliar de marca de formulário também gera um Token de verificação de
solicitação oculto para evitar a falsificação de solicitações entre sites (quando usado com o atributo
[ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger um Formulário HTML puro contra
falsificação de solicitações entre sites e o Auxiliar de marca de formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o atributo HTML action . Um
aplicativo com uma rota chamada register poderia usar a seguinte marcação para a página de registro:
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:
NOTE
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-local"
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"
[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
As anotações de dados aplicadas às propriedades Email e Password geram metadados no modelo. O Auxiliar de
marca de entrada consome os metadados do modelo e produz atributos HTML5 data-val-* (consulte Validação
de modelo). Esses atributos descrevem os validadores a serem anexados aos campos de entrada. Isso fornece
validação de jQuery e HTML5 discreto. Os atributos discretos têm o formato data-val-rule="Error Message" , em
que a regra é o nome da regra de validação (como data-val-required , data-val-email , data-val-maxlength etc.) Se
uma mensagem de erro for fornecida no atributo, ela será exibida como o valor para do atributo data-val-rule .
Também há atributos do formulário data-val-ruleName-argumentName="argumentValue" que fornecem detalhes
adicionais sobre a regra, por exemplo, data-val-maxlength-max="1024" .
Alternativas de Auxiliar HTML ao Auxiliar de marca de entrada
Html.TextBox , Html.TextBoxFor , Html.Editor e Html.EditorFor têm recursos que se sobrepõem aos di Auxiliar de
marca de entrada. O Auxiliar de marca de entrada define automaticamente o atributo type ; Html.TextBox e
Html.TextBoxFor não o fazem. Html.Editor e Html.EditorFor manipulam coleções, objetos complexos e modelos;
o Auxiliar de marca de entrada não o faz. O Auxiliar de marca de entrada, Html.EditorFor e Html.TextBoxFor são
fortemente tipados (eles usam expressões lambda); Html.TextBox e Html.Editor não usam (eles usam nomes de
expressão).
HtmlAttributes
@Html.Editor() e @Html.EditorFor() usam uma entrada ViewDataDictionary especial 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. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
Gera o seguinte:
[DataType(DataType.Password)]
public string Password { get; set; }
O método de ação:
@model Person
@{
var index = (int)ViewData["index"];
}
O modelo Views/Shared/EditorTemplates/String.cshtml:
@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>
*@
foreach deve ser usado, se possível, quando o valor está prestes a ser usado em um contexto equivalente asp-for
ou Html.DisplayFor . Em geral, for é melhor do que foreach (se o cenário permitir) porque não é necessário
alocar um enumerador; no entanto, avaliar um indexador em uma expressão LINQ pode ser caro, o que deve ser
minimizado.
NOTE
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
O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID associada ao elemento
<input> . Auxiliares de marca geram elementos id e for consistentes para que eles possam ser associados
corretamente. A legenda neste exemplo é proveniente do atributo Display . Se o modelo não contivesse um
atributo Display , a legenda seria o nome da propriedade da expressão.
O Validation Message Tag Helper é usado com o atributo asp-validation-for em um elemento HTML span.
<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.
NOTE
É 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> .
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
O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o elemento select e asp-items
especifica os elementos option. Por exemplo:
Amostra:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
O método Index inicializa o CountryViewModel , define o país selecionado e o transmite para a exibição Index .
[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
NOTE
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um modelo de exibição é mais
robusto para fornecer metadados MVC e, geralmente, menos problemático.
O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os outros atributos do Auxiliar de
marca requerem (como asp-items )
Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os elementos SelectListItem dos
valores enum .
Amostra:
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
}
}
É 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 .
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; }
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
O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma seleção. Por exemplo, o
seguinte método de ação e exibição gerarão HTML semelhante ao código acima:
O elemento <option> correto será selecionado (contém o atributo selected="selected" ) dependendo do valor
atual de Country .
Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Elemento de formulário HTML
Token de verificação de solicitação
Model binding no ASP.NET Core
Validação de modelo no ASP.NET Core MVC
Interface IAttributeAdapter
Snippets de código para este documento
Auxiliares de marca em formulários no ASP.NET Core
14/01/2019 • 28 minutes to read • Edit Online
Amostra:
O tempo de execução do MVC gera o valor do atributo action dos atributos asp-controller e asp-action do
Auxiliar de marca de formulário. O Auxiliar de marca de formulário também gera um Token de verificação de
solicitação oculto para evitar a falsificação de solicitações entre sites (quando usado com o atributo
[ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger um Formulário HTML puro contra
falsificação de solicitações entre sites e o Auxiliar de marca de formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o atributo HTML action . Um
aplicativo com uma rota chamada register poderia usar a seguinte marcação para a página de registro:
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:
NOTE
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-local"
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"
[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
As anotações de dados aplicadas às propriedades Email e Password geram metadados no modelo. O Auxiliar de
marca de entrada consome os metadados do modelo e produz atributos HTML5 data-val-* (consulte Validação
de modelo). Esses atributos descrevem os validadores a serem anexados aos campos de entrada. Isso fornece
validação de jQuery e HTML5 discreto. Os atributos discretos têm o formato data-val-rule="Error Message" , em
que a regra é o nome da regra de validação (como data-val-required , data-val-email , data-val-maxlength etc.) Se
uma mensagem de erro for fornecida no atributo, ela será exibida como o valor para do atributo data-val-rule .
Também há atributos do formulário data-val-ruleName-argumentName="argumentValue" que fornecem detalhes
adicionais sobre a regra, por exemplo, data-val-maxlength-max="1024" .
Alternativas de Auxiliar HTML ao Auxiliar de marca de entrada
Html.TextBox , Html.TextBoxFor , Html.Editor e Html.EditorFor têm recursos que se sobrepõem aos di Auxiliar de
marca de entrada. O Auxiliar de marca de entrada define automaticamente o atributo type ; Html.TextBox e
Html.TextBoxFor não o fazem. Html.Editor e Html.EditorFor manipulam coleções, objetos complexos e modelos;
o Auxiliar de marca de entrada não o faz. O Auxiliar de marca de entrada, Html.EditorFor e Html.TextBoxFor são
fortemente tipados (eles usam expressões lambda); Html.TextBox e Html.Editor não usam (eles usam nomes de
expressão).
HtmlAttributes
@Html.Editor() e @Html.EditorFor() usam uma entrada ViewDataDictionary especial 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. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
Gera o seguinte:
[DataType(DataType.Password)]
public string Password { get; set; }
O método de ação:
@model Person
@{
var index = (int)ViewData["index"];
}
O modelo Views/Shared/EditorTemplates/String.cshtml:
@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>
*@
foreach deve ser usado, se possível, quando o valor está prestes a ser usado em um contexto equivalente asp-for
ou Html.DisplayFor . Em geral, for é melhor do que foreach (se o cenário permitir) porque não é necessário
alocar um enumerador; no entanto, avaliar um indexador em uma expressão LINQ pode ser caro, o que deve ser
minimizado.
NOTE
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
O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID associada ao elemento
<input> . Auxiliares de marca geram elementos id e for consistentes para que eles possam ser associados
corretamente. A legenda neste exemplo é proveniente do atributo Display . Se o modelo não contivesse um
atributo Display , a legenda seria o nome da propriedade da expressão.
O Validation Message Tag Helper é usado com o atributo asp-validation-for em um elemento HTML span.
<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.
NOTE
É 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> .
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
O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o elemento select e asp-items
especifica os elementos option. Por exemplo:
Amostra:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
O método Index inicializa o CountryViewModel , define o país selecionado e o transmite para a exibição Index .
[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
NOTE
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um modelo de exibição é mais
robusto para fornecer metadados MVC e, geralmente, menos problemático.
O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os outros atributos do Auxiliar de
marca requerem (como asp-items )
Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os elementos SelectListItem dos
valores enum .
Amostra:
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
}
}
É 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 .
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; }
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
O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma seleção. Por exemplo, o
seguinte método de ação e exibição gerarão HTML semelhante ao código acima:
O elemento <option> correto será selecionado (contém o atributo selected="selected" ) dependendo do valor
atual de Country .
Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Elemento de formulário HTML
Token de verificação de solicitação
Model binding no ASP.NET Core
Validação de modelo no ASP.NET Core MVC
Interface IAttributeAdapter
Snippets de código para este documento
Auxiliar de marca parcial no ASP.NET Core
04/02/2019 • 5 minutes to read • Edit Online
Visão geral
O auxiliar de marca parcial é usado para renderizar uma exibição parcial em Páginas Razor e em aplicativos
MVC. Considere que ele:
Exige o ASP.NET Core 2.1 ou posterior.
É uma alternativa à sintaxe do HTML Helper.
Renderiza a exibição parcial de forma assíncrona.
As opções do HTML Helper para renderizar uma exibição parcial incluem:
@await Html.PartialAsync
@await Html.RenderPartialAsync
@Html.Partial
@Html.RenderPartial
O modelo Product é usado nos exemplos ao longo deste documento:
namespace TagHelpersBuiltIn.Models
{
public class Product
{
public int Number { get; set; }
name
O atributo name é necessário. Indica o nome ou o caminho da exibição parcial a ser renderizada. Quando é
fornecido um nome de exibição parcial, o processo descoberta de exibição é iniciado. Esse processo é ignorado
quando um caminho explícito é fornecido. Para todos os valores de name aceitáveis, consulte Descoberta de
exibição parcial.
A marcação a seguir usa um caminho explícito, indicando que _ProductPartial.cshtml deve ser carregado da
pasta Compartilhado. Usando o atributo for, um modelo é passado para a exibição parcial para associação.
<partial name="Shared/_ProductPartial.cshtml"
for="Product" />
for
O atributo for atribui uma ModelExpression a ser avaliada em relação ao modelo atual. Uma ModelExpression
infere a sintaxe @Model. . Por exemplo, for="Product" pode ser usado em vez de for="@Model.Product" . Esse
comportamento de inferência padrão é substituído usando o símbolo @ para definir uma expressão embutida.
A seguinte marcação carrega _ProductPartial.cshtml:
<partial name="_ProductPartial"
for="Product" />
using Microsoft.AspNetCore.Mvc.RazorPages;
using TagHelpersBuiltIn.Models;
namespace TagHelpersBuiltIn.Pages
{
public class ProductModel : PageModel
{
public Product Product { get; set; }
modelo
O atributo model atribui uma instância de modelo a ser passada para a exibição parcial. O atributo model não
pode ser usado com o atributo for.
Na marcação a seguir, é criada uma nova instância do objeto Product que é passada ao atributo model para
associação:
<partial name="_ProductPartial"
model='new Product { Number = 1, Name = "Test product", Description = "This is a test" }' />
view-data
O atributo view-data atribui um ViewDataDictionary a ser passado para a exibição parcial. A seguinte marcação
faz com que toda a coleção ViewData fique acessível para a exibição parcial:
@{
ViewData["IsNumberReadOnly"] = true;
}
<partial name="_ProductViewDataPartial"
for="Product"
view-data="ViewData" />
No código anterior, o valor da chave IsNumberReadOnly é definido como true e adicionado à coleção ViewData.
Consequentemente, ViewData["IsNumberReadOnly"] se torna acessível dentro da exibição parcial a seguir:
@model TagHelpersBuiltIn.Models.Product
<div class="form-group">
<label asp-for="Number"></label>
@if ((bool)ViewData["IsNumberReadOnly"])
{
<input asp-for="Number" type="number" class="form-control" readonly />
}
else
{
<input asp-for="Number" type="number" class="form-control" />
}
</div>
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" type="text" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Description"></label>
<textarea asp-for="Description" rows="4" cols="50" class="form-control"></textarea>
</div>
O auxiliar de marca parcial seguir alcança o mesmo comportamento de renderização assíncrona que o auxiliar
HTML PartialAsync . Uma instância de modelo Product é atribuída ao atributo model para associação à
exibição parcial.
Recursos adicionais
Exibições parciais no ASP.NET Core
Exibições no ASP.NET Core MVC
Auxiliares de marca em formulários no ASP.NET Core
14/01/2019 • 28 minutes to read • Edit Online
Amostra:
O tempo de execução do MVC gera o valor do atributo action dos atributos asp-controller e asp-action do
Auxiliar de marca de formulário. O Auxiliar de marca de formulário também gera um Token de verificação de
solicitação oculto para evitar a falsificação de solicitações entre sites (quando usado com o atributo
[ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger um Formulário HTML puro contra
falsificação de solicitações entre sites e o Auxiliar de marca de formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o atributo HTML action . Um
aplicativo com uma rota chamada register poderia usar a seguinte marcação para a página de registro:
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:
NOTE
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-local"
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"
[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
As anotações de dados aplicadas às propriedades Email e Password geram metadados no modelo. O Auxiliar de
marca de entrada consome os metadados do modelo e produz atributos HTML5 data-val-* (consulte Validação
de modelo). Esses atributos descrevem os validadores a serem anexados aos campos de entrada. Isso fornece
validação de jQuery e HTML5 discreto. Os atributos discretos têm o formato data-val-rule="Error Message" , em
que a regra é o nome da regra de validação (como data-val-required , data-val-email , data-val-maxlength etc.) Se
uma mensagem de erro for fornecida no atributo, ela será exibida como o valor para do atributo data-val-rule .
Também há atributos do formulário data-val-ruleName-argumentName="argumentValue" que fornecem detalhes
adicionais sobre a regra, por exemplo, data-val-maxlength-max="1024" .
Alternativas de Auxiliar HTML ao Auxiliar de marca de entrada
Html.TextBox , Html.TextBoxFor , Html.Editor e Html.EditorFor têm recursos que se sobrepõem aos di Auxiliar de
marca de entrada. O Auxiliar de marca de entrada define automaticamente o atributo type ; Html.TextBox e
Html.TextBoxFor não o fazem. Html.Editor e Html.EditorFor manipulam coleções, objetos complexos e modelos;
o Auxiliar de marca de entrada não o faz. O Auxiliar de marca de entrada, Html.EditorFor e Html.TextBoxFor são
fortemente tipados (eles usam expressões lambda); Html.TextBox e Html.Editor não usam (eles usam nomes de
expressão).
HtmlAttributes
@Html.Editor() e @Html.EditorFor() usam uma entrada ViewDataDictionary especial 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. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
Gera o seguinte:
[DataType(DataType.Password)]
public string Password { get; set; }
O método de ação:
@model Person
@{
var index = (int)ViewData["index"];
}
O modelo Views/Shared/EditorTemplates/String.cshtml:
@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>
*@
foreach deve ser usado, se possível, quando o valor está prestes a ser usado em um contexto equivalente asp-for
ou Html.DisplayFor . Em geral, for é melhor do que foreach (se o cenário permitir) porque não é necessário
alocar um enumerador; no entanto, avaliar um indexador em uma expressão LINQ pode ser caro, o que deve ser
minimizado.
NOTE
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
O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID associada ao elemento
<input> . Auxiliares de marca geram elementos id e for consistentes para que eles possam ser associados
corretamente. A legenda neste exemplo é proveniente do atributo Display . Se o modelo não contivesse um
atributo Display , a legenda seria o nome da propriedade da expressão.
O Validation Message Tag Helper é usado com o atributo asp-validation-for em um elemento HTML span.
<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.
NOTE
É 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> .
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
O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o elemento select e asp-items
especifica os elementos option. Por exemplo:
Amostra:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
O método Index inicializa o CountryViewModel , define o país selecionado e o transmite para a exibição Index .
[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
NOTE
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um modelo de exibição é mais
robusto para fornecer metadados MVC e, geralmente, menos problemático.
O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os outros atributos do Auxiliar de
marca requerem (como asp-items )
Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os elementos SelectListItem dos
valores enum .
Amostra:
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
}
}
É 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 .
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; }
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
O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma seleção. Por exemplo, o
seguinte método de ação e exibição gerarão HTML semelhante ao código acima:
O elemento <option> correto será selecionado (contém o atributo selected="selected" ) dependendo do valor
atual de Country .
Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Elemento de formulário HTML
Token de verificação de solicitação
Model binding no ASP.NET Core
Validação de modelo no ASP.NET Core MVC
Interface IAttributeAdapter
Snippets de código para este documento
Auxiliares de marca em formulários no ASP.NET Core
14/01/2019 • 28 minutes to read • Edit Online
Amostra:
O tempo de execução do MVC gera o valor do atributo action dos atributos asp-controller e asp-action do
Auxiliar de marca de formulário. O Auxiliar de marca de formulário também gera um Token de verificação de
solicitação oculto para evitar a falsificação de solicitações entre sites (quando usado com o atributo
[ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger um Formulário HTML puro contra
falsificação de solicitações entre sites e o Auxiliar de marca de formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o atributo HTML action . Um
aplicativo com uma rota chamada register poderia usar a seguinte marcação para a página de registro:
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:
NOTE
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-local"
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"
[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
As anotações de dados aplicadas às propriedades Email e Password geram metadados no modelo. O Auxiliar de
marca de entrada consome os metadados do modelo e produz atributos HTML5 data-val-* (consulte Validação
de modelo). Esses atributos descrevem os validadores a serem anexados aos campos de entrada. Isso fornece
validação de jQuery e HTML5 discreto. Os atributos discretos têm o formato data-val-rule="Error Message" , em
que a regra é o nome da regra de validação (como data-val-required , data-val-email , data-val-maxlength etc.) Se
uma mensagem de erro for fornecida no atributo, ela será exibida como o valor para do atributo data-val-rule .
Também há atributos do formulário data-val-ruleName-argumentName="argumentValue" que fornecem detalhes
adicionais sobre a regra, por exemplo, data-val-maxlength-max="1024" .
Alternativas de Auxiliar HTML ao Auxiliar de marca de entrada
Html.TextBox , Html.TextBoxFor , Html.Editor e Html.EditorFor têm recursos que se sobrepõem aos di Auxiliar de
marca de entrada. O Auxiliar de marca de entrada define automaticamente o atributo type ; Html.TextBox e
Html.TextBoxFor não o fazem. Html.Editor e Html.EditorFor manipulam coleções, objetos complexos e modelos;
o Auxiliar de marca de entrada não o faz. O Auxiliar de marca de entrada, Html.EditorFor e Html.TextBoxFor são
fortemente tipados (eles usam expressões lambda); Html.TextBox e Html.Editor não usam (eles usam nomes de
expressão).
HtmlAttributes
@Html.Editor() e @Html.EditorFor() usam uma entrada ViewDataDictionary especial 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. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
Gera o seguinte:
[DataType(DataType.Password)]
public string Password { get; set; }
O método de ação:
@model Person
@{
var index = (int)ViewData["index"];
}
O modelo Views/Shared/EditorTemplates/String.cshtml:
@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>
*@
foreach deve ser usado, se possível, quando o valor está prestes a ser usado em um contexto equivalente asp-for
ou Html.DisplayFor . Em geral, for é melhor do que foreach (se o cenário permitir) porque não é necessário
alocar um enumerador; no entanto, avaliar um indexador em uma expressão LINQ pode ser caro, o que deve ser
minimizado.
NOTE
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
O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID associada ao elemento
<input> . Auxiliares de marca geram elementos id e for consistentes para que eles possam ser associados
corretamente. A legenda neste exemplo é proveniente do atributo Display . Se o modelo não contivesse um
atributo Display , a legenda seria o nome da propriedade da expressão.
O Validation Message Tag Helper é usado com o atributo asp-validation-for em um elemento HTML span.
<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.
NOTE
É 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> .
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
O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o elemento select e asp-items
especifica os elementos option. Por exemplo:
Amostra:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
O método Index inicializa o CountryViewModel , define o país selecionado e o transmite para a exibição Index .
[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
NOTE
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um modelo de exibição é mais
robusto para fornecer metadados MVC e, geralmente, menos problemático.
O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os outros atributos do Auxiliar de
marca requerem (como asp-items )
Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os elementos SelectListItem dos
valores enum .
Amostra:
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
}
}
É 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 .
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; }
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
O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma seleção. Por exemplo, o
seguinte método de ação e exibição gerarão HTML semelhante ao código acima:
O elemento <option> correto será selecionado (contém o atributo selected="selected" ) dependendo do valor
atual de Country .
Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Elemento de formulário HTML
Token de verificação de solicitação
Model binding no ASP.NET Core
Validação de modelo no ASP.NET Core MVC
Interface IAttributeAdapter
Snippets de código para este documento
Auxiliares de marca em formulários no ASP.NET Core
14/01/2019 • 28 minutes to read • Edit Online
Amostra:
O tempo de execução do MVC gera o valor do atributo action dos atributos asp-controller e asp-action do
Auxiliar de marca de formulário. O Auxiliar de marca de formulário também gera um Token de verificação de
solicitação oculto para evitar a falsificação de solicitações entre sites (quando usado com o atributo
[ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger um Formulário HTML puro contra
falsificação de solicitações entre sites e o Auxiliar de marca de formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o atributo HTML action . Um
aplicativo com uma rota chamada register poderia usar a seguinte marcação para a página de registro:
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:
NOTE
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-local"
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"
[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
As anotações de dados aplicadas às propriedades Email e Password geram metadados no modelo. O Auxiliar de
marca de entrada consome os metadados do modelo e produz atributos HTML5 data-val-* (consulte Validação
de modelo). Esses atributos descrevem os validadores a serem anexados aos campos de entrada. Isso fornece
validação de jQuery e HTML5 discreto. Os atributos discretos têm o formato data-val-rule="Error Message" , em
que a regra é o nome da regra de validação (como data-val-required , data-val-email , data-val-maxlength etc.) Se
uma mensagem de erro for fornecida no atributo, ela será exibida como o valor para do atributo data-val-rule .
Também há atributos do formulário data-val-ruleName-argumentName="argumentValue" que fornecem detalhes
adicionais sobre a regra, por exemplo, data-val-maxlength-max="1024" .
Alternativas de Auxiliar HTML ao Auxiliar de marca de entrada
Html.TextBox , Html.TextBoxFor , Html.Editor e Html.EditorFor têm recursos que se sobrepõem aos di Auxiliar de
marca de entrada. O Auxiliar de marca de entrada define automaticamente o atributo type ; Html.TextBox e
Html.TextBoxFor não o fazem. Html.Editor e Html.EditorFor manipulam coleções, objetos complexos e modelos;
o Auxiliar de marca de entrada não o faz. O Auxiliar de marca de entrada, Html.EditorFor e Html.TextBoxFor são
fortemente tipados (eles usam expressões lambda); Html.TextBox e Html.Editor não usam (eles usam nomes de
expressão).
HtmlAttributes
@Html.Editor() e @Html.EditorFor() usam uma entrada ViewDataDictionary especial 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. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
Gera o seguinte:
[DataType(DataType.Password)]
public string Password { get; set; }
O método de ação:
@model Person
@{
var index = (int)ViewData["index"];
}
O modelo Views/Shared/EditorTemplates/String.cshtml:
@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>
*@
foreach deve ser usado, se possível, quando o valor está prestes a ser usado em um contexto equivalente asp-for
ou Html.DisplayFor . Em geral, for é melhor do que foreach (se o cenário permitir) porque não é necessário
alocar um enumerador; no entanto, avaliar um indexador em uma expressão LINQ pode ser caro, o que deve ser
minimizado.
NOTE
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
O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID associada ao elemento
<input> . Auxiliares de marca geram elementos id e for consistentes para que eles possam ser associados
corretamente. A legenda neste exemplo é proveniente do atributo Display . Se o modelo não contivesse um
atributo Display , a legenda seria o nome da propriedade da expressão.
O Validation Message Tag Helper é usado com o atributo asp-validation-for em um elemento HTML span.
<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.
NOTE
É 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> .
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
O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o elemento select e asp-items
especifica os elementos option. Por exemplo:
Amostra:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
O método Index inicializa o CountryViewModel , define o país selecionado e o transmite para a exibição Index .
[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
NOTE
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um modelo de exibição é mais
robusto para fornecer metadados MVC e, geralmente, menos problemático.
O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os outros atributos do Auxiliar de
marca requerem (como asp-items )
Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os elementos SelectListItem dos
valores enum .
Amostra:
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
}
}
É 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 .
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; }
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
O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma seleção. Por exemplo, o
seguinte método de ação e exibição gerarão HTML semelhante ao código acima:
O elemento <option> correto será selecionado (contém o atributo selected="selected" ) dependendo do valor
atual de Country .
Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Elemento de formulário HTML
Token de verificação de solicitação
Model binding no ASP.NET Core
Validação de modelo no ASP.NET Core MVC
Interface IAttributeAdapter
Snippets de código para este documento
Auxiliares de marca em formulários no
ASP.NET Core
14/01/2019 • 28 minutes to read • Edit Online
Amostra:
O tempo de execução do MVC gera o valor do atributo action dos atributos asp-controller e
asp-action do Auxiliar de marca de formulário. O Auxiliar de marca de formulário também gera um
Token de verificação de solicitação oculto para evitar a falsificação de solicitações entre sites (quando
usado com o atributo [ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger
um Formulário HTML puro contra falsificação de solicitações entre sites e o Auxiliar de marca de
formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o atributo HTML
action . Um aplicativo com uma rota chamada register poderia usar a seguinte marcação para a
página de registro:
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:
NOTE
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-local"
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"
[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
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. :
@{
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>
*@
foreach deve ser usado, se possível, quando o valor está prestes a ser usado em um contexto
equivalente asp-for ou Html.DisplayFor . Em geral, for é melhor do que foreach (se o cenário
permitir) porque não é necessário alocar um enumerador; no entanto, avaliar um indexador em uma
expressão LINQ pode ser caro, o que deve ser minimizado.
NOTE
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
O Label Tag Helper fornece os seguintes benefícios em comparação com um elemento de rótulo
HTML puro:
Você obtém automaticamente o valor do rótulo descritivo do atributo Display . O nome de
exibição desejado pode mudar com o tempo e a combinação do atributo Display e do Auxiliar
de Marca de Rótulo aplicará Display em qualquer lugar em que for usado.
Menos marcação no código-fonte
Tipagem forte com a propriedade de modelo.
Amostra:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID associada ao
elemento <input> . Auxiliares de marca geram elementos id e for consistentes para que eles
possam ser associados corretamente. A legenda neste exemplo é proveniente do atributo Display .
Se o modelo não contivesse um atributo Display , a legenda seria o nome da propriedade da
expressão.
<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.
NOTE
É 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> .
O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de validação. O
valor do atributo asp-validation-summary pode ser qualquer um dos seguintes:
ASP-VALIDATION-SUMMARY MENSAGENS DE VALIDAÇÃO EXIBIDAS
ValidationSummary.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
O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o elemento select e
asp-items especifica os elementos option. Por exemplo:
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
NOTE
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um modelo de
exibição é mais robusto para fornecer metadados MVC e, geralmente, menos problemático.
O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os outros atributos
do Auxiliar de marca requerem (como asp-items )
Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os elementos
SelectListItem dos valores enum .
Amostra:
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 .
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
O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código acima:
Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Elemento de formulário HTML
Token de verificação de solicitação
Model binding no ASP.NET Core
Validação de modelo no ASP.NET Core MVC
Interface IAttributeAdapter
Snippets de código para este documento
Layout no ASP.NET Core
29/10/2018 • 11 minutes 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 Core é chamado _Layout.cshtml. O arquivo de
layout para novos projetos do ASP.NET Core criados com os modelos:
Razor Pages: Pages/Shared/_Layout.cshtml
O layout define um modelo de nível superior para exibições no aplicativo. Os aplicativos não exigem um
layout. Os aplicativos podem definir mais de um layout, com diferentes exibições especificando diferentes
layouts.
O código a seguir mostra o arquivo de layout para um modelo de projeto criado com um controlador e
exibições:
<!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 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>
<span class="icon-bar"></span>
</button>
<a asp-page="/Index" class="navbar-brand">WebApplication1</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="/Contact">Contact</a></li>
</ul>
</div>
</div>
</nav>
<environment include="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 exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT">
</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>
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 {
@RenderSection("Scripts", required: false)
}
Se uma seção obrigatória não for encontrada, uma exceção será gerada. As exibições individuais
especificam o conteúdo a ser renderizado em uma seção usando a sintaxe Razor @section . Se uma
página ou exibição definir uma seção, ela precisará ser renderizada (ou ocorrerá um erro).
Uma definição @section de exemplo na exibição do Razor Pages:
@section Scripts {
<script type="text/javascript" src="/scripts/main.js"></script>
}
No código anterior, scripts/main.js é adicionado à seção scripts em uma página ou exibição. Outras
páginas ou exibições no mesmo aplicativo podem não exigir esse script e não definirão uma seção de
scripts.
A marcação a seguir usa o Auxiliar de Marca Parcial para renderizar _ValidationScriptsPartial.cshtml:
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
O corpo e cada seção em uma página do Razor precisam ser renderizados ou ignorados.
O arquivo não dá suporte a outros recursos do Razor, como funções e definições de seção.
Um arquivo _ViewImports.cshtml de exemplo:
@using WebApplication1
@using WebApplication1.Models
@using WebApplication1.Models.AccountViewModels
@using WebApplication1.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
O arquivo _ViewImports.cshtml para um aplicativo ASP.NET Core MVC normalmente é colocado na pasta
Pages (ou Views). Um arquivo _ViewImports.cshtml pode ser colocado em qualquer pasta, caso em que só
será aplicado a páginas ou exibições nessa pasta e em suas subpastas. Arquivos _ViewImports são
processados começando no nível raiz e, em seguida, para cada pasta até o local da página ou da exibição
em si. As configurações _ViewImports especificadas no nível raiz podem ser substituídas no nível da pasta.
Por exemplo, suponha:
O arquivo _ViewImports.cshtml no nível de raiz inclui @model MyModel1 e
@addTagHelper *, MyTagHelper1 .
Um arquivo _ViewImports.cshtml de subpasta inclui @model MyModel2 e @addTagHelper *, MyTagHelper2 .
Defina a raiz do conteúdo com o diretório atual invocando UseContentRoot dentro de Program.Main :
host.Run();
}
}
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.
Se estiver direcionando o .NET Framework, adicione o pacote Microsoft.AspNetCore.StaticFiles ao projeto. Se
você estiver direcionando para o .NET Core, o metapacote Microsoft.AspNetCore.App incluirá esse pacote.
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.
Adicione o pacote Microsoft.AspNetCore.StaticFiles ao projeto.
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:
No código anterior, o caractere til ~/ aponta para o diretório base. Para obter mais informações, confira
Diretório base.
Fornecer arquivos fora do diretório base
Considere uma hierarquia de diretórios na qual os arquivos estáticos a serem atendidos residam fora do
diretório base:
wwwroot
css
images
js
MyStaticFiles
images
banner1.svg
Uma solicitação pode acessar o arquivo banner1.svg configurando o middleware de arquivos estáticos da
seguinte maneira:
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(); // For the wwwroot folder
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
}
No código anterior, a hierarquia de diretórios MyStaticFiles é exposta publicamente por meio do segmento do
URI StaticFiles. Uma solicitação para http://<server_address>/StaticFiles/images/banner1.svg atende ao
arquivo banner1.svg.
A seguinte marcação referencia MyStaticFiles/images/banner1.svg:
[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"
});
}
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"
});
}
IMPORTANT
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 arquivos estáticos 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
NOTE
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.
WARNING
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
WARNING
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.
WARNING
Se o manipulador de arquivo estático do IIS estiver habilitado e o Módulo do ASP.NET Core não estiver configurado
corretamente, os arquivos estáticos serão atendidos. 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
Model binding no ASP.NET Core
22/11/2018 • 16 minutes to read • Edit Online
Tipos que permitem valor nulo: os tipos que permitem valor nulo são definidos como null .
No exemplo acima, o model binding 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, o model binding 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 o model
binding:
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 do model binding, a Validação ocorrerá. O model binding 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.
services.AddMvc().AddMvcOptions(options =>
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version))));
Para desabilitar a validação nas propriedades de um determinado tipo, adicione um
SuppressChildValidationMetadataProvider em Startup.ConfigureServices . Por exemplo, para
desabilitar a validação nas propriedades do tipo System.Guid :
services.AddMvc().AddMvcOptions(options =>
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid))));
NOTE
Pode haver, no máximo, um parâmetro por ação decorado com [FromBody] . O tempo de execução do
ASP.NET Core MVC delega a responsabilidade de ler o fluxo da solicitação ao formatador. Depois que o fluxo
da solicitação é lido para um parâmetro, geralmente, não é possível ler o fluxo da solicitação novamente para
associar outros parâmetros [FromBody] .
NOTE
O JsonInputFormatter é o formatador padrão e se baseia no Json.NET.
O ASP.NET Core seleciona formatadores de entrada com base no cabeçalho Content-Type e no tipo
do parâmetro, a menos que haja um atributo aplicado a ele com outra especificação. Se deseja usar
XML ou outro formato, configure-o no arquivo Startup.cs, mas talvez você precise obter primeiro uma
referência a Microsoft.AspNetCore.Mvc.Formatters.Xml usando o NuGet. O código de inicialização
deverá ter uma aparência semelhante a esta:
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.
Os atributos de validação são especificados no nível da propriedade:
[Required]
public string MyProperty { get; set; }
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.
: valida se uma propriedade de cadeia de caracteres tem, no máximo, o
[StringLength]
tamanho máximo especificado.
[Url] : valida se a propriedade tem um formato de URL.
[AcceptVerbs("Get", "Post")]
public IActionResult VerifyPhone(
[RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
if (!ModelState.IsValid)
{
return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
}
return Json(true);
}
[HttpPost]
public IActionResult CheckAge(
[BindRequired, FromQuery] int age)
{
services.AddMvc(options =>
{
options.MaxModelValidationErrors = 50;
options.AllowValidatingTopLevelNodes = false;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Estado do modelo
O estado do modelo representa erros de validação nos valores de formulário HTML enviados.
O MVC continuará validando campos até atingir o número máximo de erros (200 por padrão).
Você pode configurar esse número com o seguinte código no Startup.ConfigureServices :
Validação manual
Após a conclusão da validação e de model binding, recomendamos repetir partes disso. 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, o IsValid retorna um código ValidationResult.Success . Quando a validação falha,
um ValidationResult com uma mensagem erro será retornado:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-
validate/1.17.0/jquery.validate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-
unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"></script>
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 esses erros da maneira desejada. No entanto, o pacote
Unobtrusive Validation do jQuery pode substituir as mensagens e exibi-las de forma consistente
com as outras. Isso ocorre de forma mais evidente quando os usuários aplicam subclasses
[DataType] , como [EmailAddress] .
$.get({
url: "https://url/that/returns/a/control",
dataType: "html",
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ": Couldn't add control. " + errorThrown);
},
success: function(newInputHTML) {
var form = document.getElementById("my-form");
form.insertAdjacentHTML("beforeend", newInputHTML);
$(form).removeData("validator") // Added by jQuery Validate
.removeData("unobtrusiveValidation"); // Added by jQuery Unobtrusive
Validation
$.validator.unobtrusive.parse(form);
}
})
IClientModelValidator
Você pode criar uma lógica do lado do cliente para o atributo personalizado, e o unobtrusive
validation, que cria um adaptador para a validação do jQuery, será executado no cliente
automaticamente como parte da validação. A primeira etapa é controlar quais atributos de dados
são adicionados, pela implementação da interface IClientModelValidator , conforme mostrado
aqui:
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
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 a seguir, que adiciona um método de validação
de cliente classicmovie personalizado ao objeto validator do jQuery. Para obter uma explicação
sobre o método unobtrusive.adapters.add , confira Unobtrusive Client Validation no ASP.NET
MVC.
$.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;
});
Com o código anterior, o método classicmovie executa a validação do lado do cliente na data de
lançamento do filme. A mensagem de erro é exibida se o método retornar false .
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 .
[AcceptVerbs("Get", "Post")]
public IActionResult VerifyEmail(string email)
{
if (!_userRepository.VerifyEmail(email))
{
return Json($"Email {email} is already in use.");
}
return Json(true);
}
Agora, quando os usuários inserem um email, o JavaScript na exibição faz uma chamada remota
para ver se o email já está sendo usado e, em caso afirmativo, exibe a mensagem de erro. Caso
contrário, o usuário pode enviar o formulário como de costume.
A propriedade AdditionalFields do atributo [Remote] é útil para validação de combinações de
campos em relação aos dados no servidor. Por exemplo, se o modelo User acima tiver duas
propriedades adicionais chamadas FirstName e LastName , é recomendável verificar se não há
usuários que já têm esse par de nomes. Defina as novas propriedades, conforme mostrado no
seguinte código:
[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
public string FirstName { get; set; }
[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
public string LastName { get; set; }
AdditionalFields poderia ter sido definido de forma explícita com as cadeias de caracteres
"FirstName" e "LastName" , mas o uso do operador nameof dessa forma, simplifica a refatoração
posterior. Em seguida, o método de ação para executar a validação precisa aceitar dois
argumentos, um para o valor de FirstName e outro para o valor de LastName .
[AcceptVerbs("Get", "Post")]
public IActionResult VerifyName(string firstName, string lastName)
{
if (!_userRepository.VerifyName(firstName, lastName))
{
return Json(data: $"A user named {firstName} {lastName} already exists.");
}
AdditionalFields , como todos os argumentos de atributo, deve ser uma expressão de constante.
Portanto, você não deve usar uma cadeia de caracteres interpolada ou chamar Join para inicializar
AdditionalFields . Para cada campo adicional adicionado ao atributo [Remote] , é necessário
adicionar outro argumento ao método de ação do controlador correspondente.
Referência da sintaxe Razor para ASP.NET Core
18/01/2019 • 25 minutes to read • Edit Online
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:[email protected]">[email protected]</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>
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>
WARNING
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>
@{
var quote = "The future depends on what you do today. - Mahatma Gandhi";
}
<p>@quote</p>
@{
quote = "Hate cannot drive out hate, only love can do that. - Martin Luther King, Jr.";
}
<p>@quote</p>
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;
}
@{
var people = new Person[]
{
new Person("Weston", 33),
new Person("Johnathon", 41),
...
};
}
@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>
}
@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";
}
Mais adiante neste artigo, a seção Inspecionar a classe do Razor C# 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, confira
Modelos fortemente tipados e a @palavra-chave do modelo.
@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>
@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 Razor adicione um bloco de código C# a uma exibição:
@functions { // C# Code }
Por exemplo:
@functions {
public string GetHello()
{
return "Hello";
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;
@section
A diretiva @section é usada em conjunto com o layout para permitir que as exibições renderizem conteúdo
em partes diferentes da página HTML. Para obter mais informações, consulte Seções.
@<tag>...</tag>
O exemplo a seguir ilustra como especificar um delegado do Razor com modelo como um Func<T,TResult>.
O tipo dinâmico é especificado para o parâmetro do método encapsulado pelo delegado. Um tipo de objeto é
especificado como o valor retornado do delegado. O modelo é usado com uma List<T> de Pet que tem uma
propriedade Name .
public class Pet
{
public string Name { get; set; }
}
@{
Func<dynamic, object> petTemplate = @<p>You have a pet named <strong>@item.Name</strong>.</p>;
Saída renderizada:
Você também pode fornecer um modelo Razor embutido como um argumento para um método. No exemplo
a seguir, o método Repeat recebe um modelo Razor. O método usa o modelo para produzir o conteúdo
HTML com repetições de itens fornecidos em uma lista:
@using Microsoft.AspNetCore.Html
@functions {
public static IHtmlContent Repeat(IEnumerable<dynamic> items, int times,
Func<dynamic, IHtmlContent> template)
{
var html = new HtmlContentBuilder();
return html;
}
}
Usando a lista de animais de estimação do exemplo anterior, o método Repeat é chamado com:
List<T> de Pet .
Número de vezes que deve ser repetido cada animal de estimação.
Modelo embutido a ser usado para os itens da lista de uma lista não ordenada.
<ul>
@Repeat(pets, 3, @<li>@item.Name</li>)
</ul>
Saída renderizada:
<ul>
<li>Rin Tin Tin</li>
<li>Rin Tin Tin</li>
<li>Rin Tin Tin</li>
<li>Mr. Bigglesworth</li>
<li>Mr. Bigglesworth</li>
<li>Mr. Bigglesworth</li>
<li>K-9</li>
<li>K-9</li>
<li>K-9</li>
</ul>
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;
}
}
Componentes da exibição
Os componentes de exibição são semelhantes às exibições parciais, mas são muito mais eficientes. Os
componentes de exibição não usam o model binding e dependem apenas dos dados fornecidos durante uma
chamada a eles. Este artigo foi elaborado com controladores e modos de exibição, mas os componentes da
exibição também funcionam com o Razor Pages.
Um componente de exibição:
Renderiza uma parte em vez de uma resposta inteira.
Inclui os mesmos benefícios de capacidade de teste e separação de interesses e encontrados entre um
controlador e uma exibição.
Pode ter parâmetros e uma lógica de negócios.
É geralmente invocado em uma página de layout.
Os componentes de exibição destinam-se a qualquer momento em que há uma lógica de renderização
reutilizável muito complexa para uma exibição parcial, como:
Menus de navegação dinâmica
Nuvem de marcas (na qual ela consulta o banco de dados)
Painel de logon
Carrinho de compras
Artigos publicados recentemente
Conteúdo da barra lateral em um blog típico
Um painel de logon que é renderizado em cada página e mostra os links para logoff ou logon, dependendo
do estado de logon do usuário
Um componente de exibição consiste em duas partes: a classe (normalmente derivada de ViewComponent) e
o resultado que ele retorna (normalmente, uma exibição). Assim como os controladores, um componente de
exibição pode ser um POCO, mas a maioria dos desenvolvedores desejará aproveitar os métodos e as
propriedades disponíveis com a derivação de ViewComponent .
Os parâmetros de classe e de método na formatação Pascal Case para Auxiliares de Marcas são convertidos
em kebab case. 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]>
Para usar um componente de exibição como um Auxiliar de Marca, registre o assembly que contém o
componente de exibição usando a diretiva @addTagHelper . Se seu componente de exibição estiver em um
assembly chamado MyWebApp , adicione a seguinte diretiva ao arquivo _ViewImports.cshtml:
@addTagHelper *, MyWebApp
É possível registrar um componente de exibição como um Auxiliar de Marca a qualquer arquivo que
referencie 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:
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>
@model IEnumerable<ViewComponentSample.Models.TodoItem>
Atualize Views/ToDo/Index.cshtml:
Se a exibição PVC não é renderizada, verifique se você está chamando o componente de exibição com uma
prioridade igual a 4 ou superior.
Examinar o caminho de exibição
Altere o parâmetro de prioridade para três ou menos para que a exibição de prioridade não seja
retornada.
Renomeie temporariamente Views/ToDo/Components/PriorityList/Default.cshtml como
1Default.cshtml.
Teste o aplicativo. Você obterá o seguinte erro:
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. -->
<div>
@*
Note:
To use the below line, you need to #define no_suffix in ViewComponents/PriorityList.cs or it
won't compile.
By doing so it will cause a problem to index as there will be mutliple viewcomponents
with the same name after the compiler removes the suffix "ViewComponent"
*@
O arquivo do Razor do componente de exibição lista as cadeias de caracteres passadas para o método Invoke
(Views/Home/Components/PriorityList/Default.cshtml):
@model List<string>
<h3>Priority Items</h3>
<ul>
@foreach (var item in Model)
{
<li>@item</li>
}
</ul>
Para usar o Auxiliar de Marca, registre o assembly que contém o Componente de exibição que usa a diretiva
@addTagHelper (o componente de exibição está em um assembly chamado MyWebApp ):
@addTagHelper *, MyWebApp
A assinatura do método de PriorityList.Invoke é síncrona, mas o Razor localiza e chama o método com
Component.InvokeAsync no arquivo de marcação.
Recursos adicionais
Injeção de dependência em exibições
Compilação de arquivo do Razor no ASP.NET Core
30/01/2019 • 6 minutes to read • Edit Online
IMPORTANT
A ferramenta de pré-compilação será removida no ASP.NET Core 3.0. É recomendado migrar para o SDK do Razor.
O SDK do Razor é eficaz somente quando não há propriedades específicas de pré-compilação definidas no arquivo de
projeto. Por exemplo, definir a propriedade MvcRazorCompileOnPublish do arquivo .csproj como true desabilita o SDK
do Razor.
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation"
Version="2.0.4"
PrivateAssets="All" />
IMPORTANT
A ferramenta de pré-compilação será removida no ASP.NET Core 3.0. É recomendado migrar para o SDK do Razor.
A pré-compilação do arquivo do Razor não está disponível durante a execução de uma SCD (implantação autossuficiente)
no ASP.NET Core 2.0.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<MvcRazorCompileOnPublish>true</MvcRazorCompileOnPublish>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="1.1.0-*" />
</ItemGroup>
</Project>
Prepare o aplicativo para uma implantação dependente de estrutura com o comando de publicação da CLI do
.NET Core. Por exemplo, execute o seguinte comando na raiz do projeto:
Recursos adicionais
Exibições no ASP.NET Core MVC
Introdução a Páginas do Razor no ASP.NET Core
Exibições no ASP.NET Core MVC
Introdução a Páginas do Razor no ASP.NET Core
Exibições no ASP.NET Core MVC
SDK do Razor do ASP.NET Core
Trabalhar com o modelo de aplicativo no ASP.NET
Core
13/11/2018 • 18 minutes 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.
NOTE
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
NOTE
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.
NOTE
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 .
NOTE
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
}
}
}
TIP
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.";
}
}
}
NOTE
Saiba mais sobre migração do ASP.NET Web API.
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 no ASP.NET Core
30/01/2019 • 38 minutes 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. Eles são executados antes do model binding,
portanto, podem influenciar o model binding.
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. Os filtros de ação não são compatíveis com o Razor Pages.
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 é executado com êxito.
Eles são úteis para a lógica que precisa envolver a execução da exibição ou do formatador.
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.
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.
using FiltersSample.Helper;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
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 ou o próximo filtro 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
ActionFilterAttribute implementa IActionFilter , IResultFilter e os respectivos equivalentes assíncronos.
NOTE
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 IFilterMetadata. Portanto, uma instância IFilterFactory pode ser usada como
uma instância IFilterMetadata 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 IFilterMetadata que será invocada. Isso fornece um
design flexível, porque 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:
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;
[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
NOTE
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 de Order padrão como 0. Para os filtros
internos, o escopo determina a ordem, a menos que você defina Order com 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
NOTE
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
Tipos de implementação do filtro de serviço são registrados em DI. Um ServiceFilterAttribute recupera
uma instância do filtro da DI. Adicione o ServiceFilterAttribute ao contêiner em
Startup.ConfigureServices e faça uma referência a ele em um atributo [ServiceFilter] :
services.AddScoped<AddHeaderFilterWithDi>();
}
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
return View();
}
Ao usar ServiceFilterAttribute , configurar IsReusable é uma dica de que a instância de filtro pode ser
reutilizada fora do escopo de solicitação em que ele foi criada. A estrutura não fornece garantias de que
uma única instância do filtro vá ser criada ou que o filtro não será solicitado novamente do contêiner de DI
em algum momento posterior. Evite usar IsReusable ao usar um filtro que dependa dos serviços com um
tempo de vida diferente de singleton.
Usar ServiceFilterAttribute sem registrar o tipo de filtro gera uma exceção:
Ao usar TypeFilterAttribute , configurar IsReusable é uma dica de que a instância de filtro pode ser
reutilizada fora do escopo de solicitação em que ele foi criada. A estrutura não fornece nenhuma garantia de
que uma única instância do filtro será criada. Evite usar IsReusable ao usar um filtro que dependa dos
serviços com um tempo de vida diferente de singleton.
O exemplo a seguir demonstra como passar argumentos para um tipo usando TypeFilterAttribute :
[TypeFilter(typeof(LogConstantFilter),
Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
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.
São os primeiros filtros a serem executados no pipeline de filtro.
Têm um método anterior, mas não têm um método posterior.
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.
Você não deve gerar exceções dentro de filtros de autorização, pois nada tratará a exceção (os filtros de
exceção não as tratarão). Considere a possibilidade de emitir um desafio quando ocorrer uma exceção.
Saiba mais sobre Autorização.
Filtros de recurso
Implementam a interface IResourceFilter ou IAsyncResourceFilter .
Suas execuções encapsulam a maior parte do pipeline de filtro.
Somente os Filtros de autorização são executados antes dos Filtros de recurso.
Os filtros de recursos são úteis para causar um curto-circuito na maior parte do trabalho que uma
solicitação faz. Por exemplo, um filtro de cache poderá evitar o restante do pipeline se a resposta estiver no
cache.
O filtro de recurso com curto-circuito mostrado anteriormente é um exemplo de filtro de recurso. Outro
exemplo é DisableFormValueModelBindingAttribute:
Essa opção impedirá o model binding de acessar os dados do formulário.
Ela é útil para uploads de arquivos grandes e para impedir que o formulário seja lido na memória.
Filtros de ação
IMPORTANT
Os filtros de ação não se aplicam ao Razor Pages. O Razor Pages é compatível com IPageFilter e IAsyncPageFilter.
Para obter mais informações, confira Métodos de filtro para Páginas Razor.
Filtros de ação:
Implementam a interface IActionFilter ou IAsyncActionFilter .
A execução deles envolve a execução de métodos de ação.
Veja um exemplo de filtro de ação:
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. Definindo ActionExecutedContext.Exception como nulo:
'Trata' efetivamente uma exceção.
ActionExectedContext.Result é executado como se fosse retornado normalmente do método de ação.
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 do desenvolvedor personalizada para
exibir detalhes sobre exceções que ocorrem quando o aplicativo está em desenvolvimento:
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
Filtros de exceção:
Não têm eventos anteriores nem posteriores.
Implementam OnException ou OnExceptionAsync .
Tratam as exceções sem tratamento que ocorrem na criação do controlador, no model binding, nos filtros
de ação ou nos métodos de ação.
Não capturam as exceções que ocorrem em Filtros de recurso, em Filtros de resultado ou na execução
do Resultado de 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. Um Filtro de exceção não pode transformar
uma exceção em "êxito". Somente um filtro de ação pode fazer isso.
NOTE
No ASP.NET Core 1.1, a resposta não será enviada se você definir ExceptionHandled como true 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 as exceções que ocorrem nas ações de MVC.
Não são tão flexíveis quanto o middleware de tratamento de erro.
Prefira o middleware para tratamento de exceção. Use filtros de exceção somente quando você precisar
fazer o tratamento de erro de forma diferente com base na ação de 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.
O ExceptionFilterAttribute pode tornar-se uma subclasse.
Filtros de resultado
Implementam a interface IResultFilter ou IAsyncResultFilter .
A execução deles envolve a execução de resultados de 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. A
geração de uma exceção vai:
Impedir a execução do resultado da ação e dos próximos filtros.
Ser tratada como uma falha e não como um resultado com êxito.
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
do model binding e depois do restante do pipeline.
Próximas ações
Confira Métodos de filtro do Razor Pages
Para fazer experiências com filtros, baixe, teste e modifique o exemplo do Github.
Áreas no ASP.NET Core
10/09/2018 • 9 minutes to read • Edit Online
As áreas fornecem uma maneira de particionar um aplicativo Web ASP.NET Core MVC grande em
agrupamentos funcionais menores. Uma área é efetivamente uma estrutura MVC dentro de um aplicativo. Em
um projeto MVC, componentes lógicos como Modelo, Controlador e Exibição são mantidos em pastas
diferentes e o MVC usa convenções de nomenclatura para criar a relação entre esses componentes. Para um
aplicativo grande, pode ser vantajoso particionar o aplicativo em áreas de nível alto separadas de
funcionalidade. Por exemplo, um aplicativo de comércio eletrônico com várias unidades de negócios, como
check-out, cobrança e pesquisa, etc. Cada uma dessas unidades têm suas próprias exibições de componente
lógico, controladores e modelos. Nesse cenário, você pode usar Áreas para particionar fisicamente os
componentes de negócios no mesmo projeto.
Uma área pode ser definida como as menores unidades funcionais em um projeto do ASP.NET Core MVC
com seu próprio conjunto de controladores, exibições e modelos.
Considere o uso de Áreas em um projeto MVC quando:
O aplicativo é composto por vários componentes funcionais de alto nível que devem ser separados
logicamente
Você deseja particionar o projeto MVC para que cada área funcional possa ser trabalhada de forma
independente
Recursos de área:
Um aplicativo ASP.NET Core MVC pode ter qualquer quantidade de áreas.
Cada área tem seus próprios controladores, modelos e exibições.
As áreas permitem organizar projetos MVC grandes em vários componentes de alto nível que podem
ser trabalhados de forma independente.
As áreas dão suporte a vários controladores com o mesmo nome, desde que eles tenham áreas
diferentes.
Vamos dar uma olhada em um exemplo para ilustrar como as Áreas são criadas e usadas. Digamos que você
tenha um aplicativo de loja que tem dois agrupamentos distintos de controladores e exibições: Produtos e
Serviços. Uma estrutura de pastas comum para isso usando áreas do MVC tem a aparência mostrada abaixo:
Nome do projeto
Áreas
Produtos
Controladores
HomeController.cs
ManageController.cs
Exibições
Home
Index.cshtml
Gerenciar
Index.cshtml
Serviços
Controladores
HomeController.cs
Exibições
Home
Index.cshtml
Quando o MVC tenta renderizar uma exibição em uma Área, por padrão, ele tenta procurar nos seguintes
locais:
/Areas/<Area-Name>/Views/<Controller-Name>/<Action-Name>.cshtml
/Areas/<Area-Name>/Views/Shared/<Action-Name>.cshtml
/Views/Shared/<Action-Name>.cshtml
Esses são os locais padrão que podem ser alterados por meio do AreaViewLocationFormats no
Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions .
Por exemplo, no código abaixo, em vez de ter o nome da pasta como 'Areas', ele foi alterado para 'Categories'.
services.Configure<RazorViewEngineOptions>(options =>
{
options.AreaViewLocationFormats.Clear();
options.AreaViewLocationFormats.Add("/Categories/{2}/Views/{1}/{0}.cshtml");
options.AreaViewLocationFormats.Add("/Categories/{2}/Views/Shared/{0}.cshtml");
options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
});
Uma coisa importante a ser observada é que a estrutura da pasta Views é a única que é considerada
importante aqui e, o conteúdo do restante das pastas como Controllers e Models não importa. Por exemplo,
você não precisa ter uma pasta Controllers e Models. Isso funciona porque o conteúdo de Controllers e Models
é apenas um código que é compilado em uma .dll, enquanto o conteúdo de Views não é, até que uma
solicitação para essa exibição seja feita.
Depois de definir a hierarquia de pastas, você precisa informar ao MVC que cada controlador está associado a
uma área. Faça isso decorando o nome do controlador com o atributo [Area] .
...
namespace MyStore.Areas.Products.Controllers
{
[Area("Products")]
public class HomeController : Controller
{
// GET: /Products/Home/Index
public IActionResult Index()
{
return View();
}
// GET: /Products/Home/Create
public IActionResult Create()
{
return View();
}
}
}
Configure uma definição de rota que funciona com as áreas recém-criadas. O artigo Rota para ações do
controlador apresenta detalhes de 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
30/10/2018 • 7 minutes to read • Edit Online
// OR
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
services.AddMvc()
.ConfigureApplicationPartManager(apm => apm.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 . Se isso não for feito, 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 poderá resultar em um
comportamento incorreto do aplicativo.
Se tiver um assembly que contém controladores que você não deseja usar, remova-o do ApplicationPartManager :
services.AddMvc()
.ConfigureApplicationPartManager(apm =>
{
var dependentLibrary = apm.ApplicationParts
.FirstOrDefault(part => part.Name == "DependentLibrary");
if (dependentLibrary != null)
{
apm.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(apm =>
apm.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:
Model binding personalizado no ASP.NET Core
22/11/2018 • 12 minutes 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, o model binding 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 .
A classe AuthorEntityBinder a seguir associa um parâmetro Author efetuando fetch da entidade de uma fonte de
dados usando o Entity Framework Core e um 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(
modelName,
"Author Id must be an integer.");
return Task.CompletedTask;
}
NOTE
A classe AuthorEntityBinder precedente é destinada a ilustrar um associador de modelos personalizado. A classe não é
destinada a ilustrar as melhores práticas para um cenário de pesquisa. Para pesquisa, associe o authorId e consulte o
banco de dados em um método de ação. Essa abordagem separa falhas de model binding de casos de NotFound .
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 Author 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 for adicionado à coleção do MVC de provedores, não será
necessário usar o atributo ModelBinder nos parâmetros Author ou de tipo 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());
});
}
É recomendável que você teste seu aplicativo usando a versão mais recente (
CompatibilityVersion.Version_2_2 ). Estimamos que a maioria dos aplicativos não terão alterações da falha de
comportamento usando a versão mais recente.
Os aplicativos que chamam SetCompatibilityVersion(CompatibilityVersion.Version_2_0) são protegidos
contra as possíveis alterações da falha de comportamento apresentadas no ASP.NET Core 2.1 MVC e nas
versões 2.x posteriores. Essa proteção:
Não se aplica a todas as alterações da 2.1 e posteriores, ela é direcionada às possíveis alterações da falha
de comportamento do tempo de execução do ASP.NET Core no subsistema de MVC.
Não se estende à próxima versão principal.
A compatibilidade padrão para aplicativos ASP.NET Core 2.1 e 2.x posteriores que não é chamada é a
SetCompatibilityVersion compatibilidade 2.0. Ou seja, não chamar SetCompatibilityVersion é o mesmo que
chamar SetCompatibilityVersion(CompatibilityVersion.Version_2_0) .
O código a seguir define o modo de compatibilidade para o ASP.NET Core 2.2, exceto para os seguintes
comportamentos:
AllowCombiningAuthorizeFilters
InputFormatterExceptionPolicy
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
// Include the 2.2 behaviors
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
// Except for the following.
.AddMvcOptions(options =>
{
// Don't combine authorize filters (keep 2.0 behavior).
options.AllowCombiningAuthorizeFilters = false;
// All exceptions thrown by an IInputFormatter are treated
// as model state errors (keep 2.0 behavior).
options.InputFormatterExceptionPolicy =
InputFormatterExceptionPolicy.AllExceptions;
});
}
Para aplicativos que encontram alterações da falha de comportamento, o uso das opções de compatibilidade
apropriadas:
Permite que você use a versão mais recente e recuse alterações da falha de comportamento específicas.
Tenha tempo para atualizar seu aplicativo para que ele funcione com as alterações mais recentes.
Os comentários do código-fonte da classe MvcOptions têm uma boa explicação sobre o que mudou e por
que as alterações são uma melhoria para a maioria dos usuários.
Futuramente, haverá uma versão 3.0 do ASP.NET Core. Os comportamentos antigos compatíveis por meio
de opções de compatibilidade serão removidos na versão 3.0. Consideramos que essas são alterações
positivas que beneficiam quase todos os usuários. Com a introdução dessas alterações, a maioria dos
aplicativos poderá se beneficiar agora e os demais terão tempo para serem atualizados.
Criar APIs Web com o ASP.NET Core
17/01/2019 • 13 minutes to read • Edit Online
[HttpGet]
public async Task<ActionResult<List<Pet>>> GetAllAsync()
{
return await _repository.GetPetsAsync();
}
[HttpGet("{id}")]
[ProducesResponseType(404)]
public async Task<ActionResult<Pet>> GetByIdAsync(int id)
{
var pet = await _repository.GetPetAsync(id);
if (pet == null)
{
return NotFound();
}
return pet;
}
[HttpPost]
[ProducesResponseType(400)]
public async Task<ActionResult<Pet>> CreateAsync(Pet pet)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await _repository.AddPetAsync(pet);
return CreatedAtAction(nameof(GetByIdAsync),
new { id = pet.Id }, pet);
}
}
[Produces("application/json")]
[Route("api/[controller]")]
public class PetsController : ControllerBase
{
private readonly PetsRepository _repository;
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Pet>), 200)]
public async Task<IActionResult> GetAllAsync()
{
var pets = await _repository.GetPetsAsync();
return Ok(pets);
}
[HttpGet("{id}")]
[ProducesResponseType(typeof(Pet), 200)]
[ProducesResponseType(404)]
public async Task<IActionResult> GetByIdAsync(int id)
{
var pet = await _repository.GetPetAsync(id);
if (pet == null)
{
return NotFound();
}
return Ok(pet);
}
[HttpPost]
[ProducesResponseType(typeof(Pet), 201)]
[ProducesResponseType(400)]
public async Task<IActionResult> CreateAsync([FromBody] Pet pet)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await _repository.AddPetAsync(pet);
return CreatedAtAction(nameof(GetByIdAsync),
new { id = pet.Id }, pet);
}
}
A classe ControllerBase fornece acesso a várias propriedades e métodos. No código anterior, os exemplos
incluem BadRequest(ModelStateDictionary) e CreatedAtAction(String, Object, Object). Esses métodos são
chamados em métodos de ação para retornar os códigos de status HTTP 400 e 201, respectivamente. A
propriedade ModelState, também fornecida pelo ControllerBase , é acessada para executar a validação do
modelo de solicitação.
Uma versão de compatibilidade do 2.1 ou posterior, definida por meio de SetCompatibilityVersion, é necessária
para usar esse atributo no nível do controlador. Por exemplo, o código realçado em Startup.ConfigureServices
define o sinalizador de compatibilidade 2.1:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Para obter mais informações, consulte Versão de compatibilidade do ASP.NET Core MVC.
No ASP.NET Core 2.2 ou posterior, o atributo [ApiController] pode ser aplicado a um assembly. A anotação
dessa maneira aplica o comportamento da API Web para todos os controladores no assembly. Lembre-se de que
não é possível recusar controladores individuais. Como uma recomendação, os atributos de nível de assembly
devem ser aplicados à classe Startup :
[assembly: ApiController]
namespace WebApiSample.Api._22
{
public class Startup
{
Uma versão de compatibilidade do 2.2 ou posterior, definida por meio de SetCompatibilityVersion, é necessária
para usar esse atributo no nível do assembly.
Geralmente, o atributo [ApiController] é associado ao ControllerBase para habilitar o comportamento
específico de REST para controladores. ControllerBase fornece acesso a métodos como NotFound e File.
Outra abordagem é criar uma classe de base de controlador personalizada anotada com o atributo
[ApiController] :
[ApiController]
public class MyBaseController
{
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[404].Link =
"https://httpstatuses.com/404";
});
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
});
Com um sinalizador de compatibilidade do 2.2 ou posterior, o tipo de resposta padrão para respostas HTTP 400 é
ValidationProblemDetails. O tipo ValidationProblemDetails está em conformidade com a especificação RFC
7807. Defina a propriedade SuppressUseValidationProblemDetailsForInvalidModelStateResponses como true para
retornar o formato de erro do ASP.NET Core 2.1 de SerializableError. Adicione o seguinte código em
Startup.ConfigureServices :
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options =>
{
options
.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
});
Sem o atributo [ApiController], os atributos da origem da associação são definidos explicitamente. Sem
[ApiController] ou outros atributos de origem de associação, como [FromQuery] , o tempo de execução do
ASP.NET Core tenta usar o associador de modelos de objeto complexo. O associador de modelos de objeto
complexo extrai os dados dos provedores de valor (que têm uma ordem definida). Por exemplo, o "associador de
modelos de corpo" está sempre definido como aceitar.
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:
[HttpGet]
public async Task<ActionResult<List<Product>>> GetAsync(
[FromQuery] bool discontinuedOnly = false)
{
List<Product> products = null;
if (discontinuedOnly)
{
products = await _repository.GetDiscontinuedProductsAsync();
}
else
{
products = await _repository.GetProductsAsync();
}
return products;
}
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
interno complexo com um significado especial, como IFormCollection e CancellationToken. O código de
inferência da origem da associação ignora esses tipos especiais. [FromBody] não é inferido para tipos
simples, como string ou int . Portanto, o atributo [FromBody] deve ser usado para tipos simples quando
essa funcionalidade for necessária. 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:
[HttpPost]
public IActionResult Action2(Product product,
[FromBody] Order order) => null;
[HttpPost]
public IActionResult Action3([FromBody] Product product,
[FromBody] Order order) => null;
NOTE
No ASP.NET Core 2.1, os parâmetros de tipo de coleção, como listas e matrizes, são inferidos incorretamente como
[FromQuery]. [FromBody] deverá ser usado para esses parâmetros se eles forem vinculados ao corpo da solicitação.
Esse comportamento é corrigido no ASP.NET Core 2.2 ou posterior, onde os parâmetros do tipo de coleção são
inferidos para serem vinculados ao corpo por padrão.
[FromForm ] é inferido para parâmetros de ação do tipo 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 mais de uma rota 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.
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[404].Link =
"https://httpstatuses.com/404";
});
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
});
options.ClientErrorMapping[404].Link =
"https://httpstatuses.com/404";
});
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
});
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
As ações são inacessíveis por meio de rotas convencionais definidas em UseMvc ou por
UseMvcWithDefaultRoute em Startup.Configure .
Respostas de detalhes de problema para códigos de status de erro
No ASP.NET Core 2.2 ou posterior, o MVC transforma um resultado de erro (um resultado com o código de
status 400 ou superior) em um resultado com ProblemDetails. ProblemDetails é:
Um tipo baseado na especificação RFC 7807.
Um formato padronizado para especificar detalhes do erro legível por computador em uma resposta HTTP.
Considere o seguinte código em uma ação do controlador:
if (product == null)
{
return NotFound();
}
A resposta HTTP para NotFound tem um código de status 404 com um corpo ProblemDetails . Por exemplo:
{
type: "https://tools.ietf.org/html/rfc7231#section-6.5.4",
title: "Not Found",
status: 404,
traceId: "0HLHLV31KRN83:00000001"
}
O recurso de detalhes do problema requer um sinalizador de compatibilidade de 2.2 ou posterior. O
comportamento padrão é desabilitado quando a propriedade SuppressMapClientErrors está definida como true .
Adicione o seguinte código em Startup.ConfigureServices :
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[404].Link =
"https://httpstatuses.com/404";
});
Use a propriedade ClientErrorMapping para configurar o conteúdo da resposta ProblemDetails . Por exemplo, o
código a seguir atualiza a propriedade type para respostas 404:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[404].Link =
"https://httpstatuses.com/404";
});
Recursos adicionais
Tipos de retorno de ação do controlador na API Web ASP.NET Core
Formatadores personalizados na API Web ASP.NET Core
Formatar dados de resposta na API Web ASP.NET Core
Páginas de ajuda da API Web ASP.NET Core com o Swagger/OpenAPI
Roteamento para ações do controlador no ASP.NET Core
Tutorial: Criar uma API Web com o ASP.NET Core
MVC
30/01/2019 • 28 minutes 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
Se você receber uma caixa de diálogo perguntando se você deve confiar no certificado do IIS Express, selecione
Sim. Na caixa de diálogo Aviso de Segurança exibida em seguida, selecione Sim.
O seguinte JSON é retornado:
["value1","value2"]
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 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)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP
//request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for
// production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
O código anterior:
Remove as declarações using não utilizadas.
Adiciona o contexto de banco de dados ao contêiner de DI.
Especifica que o contexto de banco de dados usará um banco de dados em memória.
Adicionar um controlador
Visual Studio
Visual Studio Code/Visual Studio para Mac
Clique com o botão direito do mouse na pasta Controllers.
Selecione Adicionar > Novo Item.
Na caixa de diálogo Adicionar Novo Item, selecione o modelo Classe do Controlador de API.
Dê à classe o nome TodoController e selecione Adicionar.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TodoApi.Models;
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TodoController : ControllerBase
{
private readonly TodoContext _context;
if (_context.TodoItems.Count() == 0)
{
// Create a new TodoItem if collection is empty,
// which means you can't delete all TodoItems.
_context.TodoItems.Add(new TodoItem { Name = "Item1" });
_context.SaveChanges();
}
}
}
}
O código anterior:
Define uma classe de controlador de API sem métodos.
Decore a classe com o atributo [ApiController] . Esse atributo indica se o controlador responde às
solicitações da API Web. Para obter informações sobre comportamentos específicos habilitados pelo atributo,
confira Anotação com o atributo ApiController.
Usa a DI 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.
Adiciona um item chamado Item1 ao banco de dados se o banco de dados está vazio. Esse código está no
construtor, de modo que ele seja executado sempre que há uma nova solicitação HTTP. Se você excluir todos
os itens, o construtor criará Item1 novamente na próxima vez que um método de API for chamado. Portanto,
pode parecer que a exclusão não funcionou quando ela realmente funcionou.
// GET: api/Todo
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Substitua [controller] pelo nome do controlador, que é o nome de classe do controlador menos o sufixo
"Controlador" por convenção. Para esta amostra, o nome da classe do controlador é TodoController e,
portanto, o nome do controlador é "todo". O roteamento do ASP.NET Core não diferencia maiúsculas de
minúsculas.
Se o atributo [HttpGet] tiver um modelo de rota (por exemplo, [HttpGet("products")] ), acrescente isso
ao caminho. Esta amostra não usa um modelo. Para obter mais informações, confira Roteamento de
atributo com atributos Http[Verb].
No método GetTodoItem a seguir, "{id}" é uma variável de espaço reservado para o identificador exclusivo do
item pendente. Quando GetTodoItem é invocado, o valor de "{id}" na URL é fornecido para o método no
parâmetro id .
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
Valores de retorno
O tipo de retorno dos métodos GetTodoItems e GetTodoItem é o tipo ActionResult<T>. O ASP.NET Core
serializa automaticamente o objeto em JSON e grava o JSON no corpo da mensagem de resposta. O código de
resposta para esse tipo de retorno é 200, supondo que não haja nenhuma exceção sem tratamento. As exceções
sem tratamento são convertidas em erros 5xx.
Os tipos de retorno ActionResult podem representar uma ampla variedade de códigos de status HTTP. Por
exemplo, GetTodoItem pode retornar dois valores de status diferentes:
Se nenhum item corresponder à ID solicitada, o método retornará um código de erro 404 NotFound.
Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar item resulta em uma
resposta HTTP 200.
WARNING
Habilite novamente a verificação do certificado SSL depois de testar o controlador.
O código anterior é um método HTTP POST, conforme indicado pelo atributo [HttpPost]. O método obtém o
valor do item pendente no corpo da solicitação HTTP.
O método CreatedAtAction :
retorna um código de status HTTP 201 em caso de êxito. HTTP 201 é a resposta padrão para um método
HTTP POST que cria um novo recurso no servidor.
Adiciona um cabeçalho Location à resposta. O cabeçalho Location especifica o URI do item de tarefas
pendentes recém-criado. Para obter mais informações, confira 10.2.2 201 Criado.
Faz referência à ação GetTodoItem para criar o URI de Location do cabeçalho. A palavra-chave nameof
do C# é usada para evitar o hard-coding do nome da ação, na chamada CreatedAtAction .
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
{
"name":"walk dog",
"isComplete":true
}
Selecione Enviar.
Se você receber um erro 405 Método Não Permitido, provavelmente, esse será o resultado da não
compilação do projeto após a adição do método PostTodoItem .
Testar o URI do cabeçalho de local
Selecione a guia Cabeçalhos no painel Resposta.
Copie o valor do cabeçalho Local:
Defina o método como GET.
Cole o URI (por exemplo, https://localhost:5001/api/Todo/2 )
Selecione Enviar.
// PUT: api/Todo/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
{
if (id != item.Id)
{
return BadRequest();
}
_context.Entry(item).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}
PutTodoItem é semelhante a PostTodoItem , exceto pelo uso de HTTP PUT. A resposta é 204 (Sem conteúdo). De
acordo com a especificação de HTTP, uma solicitação PUT exige que o cliente envie a entidade inteira atualizada,
não apenas as alterações. Para dar suporte a atualizações parciais, use HTTP PATCH.
Testar o método PutTodoItem
Atualize o item pendente que tem a ID = 1 e defina seu nome como "feed fish":
{
"ID":1,
"name":"feed fish",
"isComplete":true
}
// DELETE: api/Todo/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Save">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Adicione um arquivo JavaScript chamado site.js ao diretório wwwroot. Substitua seu conteúdo pelo código a
seguir:
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
tr.appendTo(tBody);
});
todos = data;
}
});
}
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function(key, item) {
if (item.id === id) {
$("#edit-name").val(item.name);
$("#edit-id").val(item.id);
$("#edit-isComplete")[0].checked = item.isComplete;
}
});
$("#spoiler").css({ display: "block" });
}
$(".my-form").on("submit", function() {
const item = {
name: $("#edit-name").val(),
isComplete: $("#edit-isComplete").is(":checked"),
id: $("#edit-id").val()
};
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$("#spoiler").css({ display: "none" });
}
Uma alteração nas configurações de inicialização do projeto ASP.NET Core pode ser necessária para testar a
página HTML localmente:
Abra Properties\launchSettings.json.
Remova a propriedade launchUrl para forçar o aplicativo a ser aberto em index.html, o arquivo padrão do
projeto.
Há várias maneiras de obter o jQuery. No snippet anterior, a biblioteca é carregada de uma CDN.
Esta amostra chama todos os métodos CRUD da API. Veja a seguir explicações das chamadas à API.
Obter uma lista de itens pendentes
A função ajax do jQuery envia uma solicitação GET para a API, que retorna o JSON que representa uma matriz
de itens pendentes. A função de retorno de chamada success será invocada se a solicitação for bem-sucedida.
No retorno de chamada, o DOM é atualizado com as informações do item pendente.
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
tr.appendTo(tBody);
});
todos = data;
}
});
}
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
Recursos adicionais
Exibir ou baixar o código de exemplo para este tutorial. Consulte como baixar.
Para obter mais informações, consulte os seguintes recursos:
Criar APIs Web com o ASP.NET Core
Páginas de ajuda da API Web ASP.NET Core com o Swagger/OpenAPI
Páginas Razor do ASP.NET Core com EF Core – série de tutoriais
Roteamento para ações do controlador no ASP.NET Core
Tipos de retorno de ação do controlador na API Web ASP.NET Core
Implantar aplicativos ASP.NET Core no Serviço de Aplicativo do Azure
Hospedar e implantar o ASP.NET Core
Próximas etapas
Neste tutorial, você aprendeu como:
Criar um projeto de API Web.
Adicionar uma classe de modelo.
Criar o contexto de banco de dados.
Registrar o contexto de banco de dados.
Adicionar um controlador.
Adicionar métodos CRUD.
Configurar o roteamento e caminhos de URL.
Especificar os valores retornados.
Chamar a API Web com o Postman.
Chamar a API Web com o jQuery.
Avance para o próximo tutorial para saber como gerar páginas de ajuda da API:
Introdução ao Swashbuckle e ao ASP.NET Core
Criar uma API Web com o ASP.NET Core e o
MongoDB
06/02/2019 • 13 minutes to read • Edit Online
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
SDK 2.2 ou posterior do .NET Core
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
MongoDB
Configurar o MongoDB
Se usar o Windows, o MongoDB será instalado em C:\Arquivos de Programas\MongoDB por padrão. Adicione
C:\Arquivos de Programas\MongoDB\Servidor\<número_de_versão>\bin à variável de ambiente Path . Essa
alteração possibilita o acesso ao MongoDB a partir de qualquer lugar em seu computador de desenvolvimento.
Use o Shell do mongo nas etapas a seguir para criar um banco de dados, fazer coleções e armazenar documentos.
Para saber mais sobre os comandos de Shell do mongo, consulte Como trabalhar com o Shell do mongo.
1. Escolha um diretório no seu computador de desenvolvimento para armazenar os dados. Por exemplo,
C:\BooksData no Windows. Crie o diretório se não houver um. O Shell do mongo não cria novos diretórios.
2. Abra um shell de comando. Execute o comando a seguir para se conectar ao MongoDB na porta padrão
27017. Lembre-se de substituir <data_directory_path> pelo diretório escolhido na etapa anterior.
3. Abra outra instância do shell de comando. Conecte-se ao banco de dados de testes padrão executando o
seguinte comando:
mongo
use BookstoreDb
Se ele ainda não existir, um banco de dados chamado BookstoreDb será criado. Se o banco de dados existir,
a conexão dele será aberta para transações.
5. Crie uma coleção Books usando o seguinte comando:
db.createCollection('Books')
{ "ok" : 1 }
6. Defina um esquema para a coleção Books e insira dois documentos usando o seguinte comando:
db.Books.insertMany([{'Name':'Design Patterns','Price':54.93,'Category':'Computers','Author':'Ralph
Johnson'}, {'Name':'Clean Code','Price':43.15,'Category':'Computers','Author':'Robert C. Martin'}])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5bfd996f7b8e48dc15ff215d"),
ObjectId("5bfd996f7b8e48dc15ff215e")
]
}
db.Books.find({}).pretty()
{
"_id" : ObjectId("5bfd996f7b8e48dc15ff215d"),
"Name" : "Design Patterns",
"Price" : 54.93,
"Category" : "Computers",
"Author" : "Ralph Johnson"
}
{
"_id" : ObjectId("5bfd996f7b8e48dc15ff215e"),
"Name" : "Clean Code",
"Price" : 43.15,
"Category" : "Computers",
"Author" : "Robert C. Martin"
}
O esquema adiciona uma propriedade _id gerada automaticamente do tipo ObjectId para cada
documento.
O banco de dados está pronto. Você pode começar a criar a API Web do ASP.NET Core.
Adicionar um modelo
1. Adicione um diretório Modelos à raiz do projeto.
2. Adicione uma classe Book ao diretório Modelos com o seguinte código:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace BooksApi.Models
{
public class Book
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("Name")]
public string BookName { get; set; }
[BsonElement("Price")]
public decimal Price { get; set; }
[BsonElement("Category")]
public string Category { get; set; }
[BsonElement("Author")]
public string Author { get; set; }
}
}
Outras propriedades na classe são anotadas com o atributo [BsonElement] . O valor do atributo representa o nome
da propriedade da coleção do MongoDB.
using System.Collections.Generic;
using System.Linq;
using BooksApi.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
namespace BooksApi.Services
{
public class BookService
{
private readonly IMongoCollection<Book> _books;
{
"ConnectionStrings": {
"BookstoreDb": "mongodb://localhost:27017"
},
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
O registro de serviço anterior é necessário para dar suporte à injeção do construtor no consumo de classes.
A classe BookService usa os seguintes membros MongoDB.Driver para executar operações CRUD em relação ao
banco de dados:
MongoClient – Lê a instância do servidor para executar operações de banco de dados. O construtor dessa
classe é fornecido na cadeia de conexão do MongoDB:
IMongoDatabase – Representa o banco de dados Mongo para execução de operações. Este tutorial usa o
método genérico GetCollection<T>(collection) na interface para obter acesso a dados em uma coleção
específica. Operações CRUD podem ser executadas em relação à coleção depois que esse método é
chamado. Na chamada de método GetCollection<T>(collection) :
collection representa o nome da coleção.
T representa o tipo de objeto CLR armazenado na coleção.
Adicionar um controlador
1. Adicione uma classe BooksController ao diretório Controladores com o seguinte código:
using System.Collections.Generic;
using BooksApi.Models;
using BooksApi.Services;
using Microsoft.AspNetCore.Mvc;
namespace BooksApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly BookService _bookService;
[HttpGet]
public ActionResult<List<Book>> Get()
{
return _bookService.Get();
}
if (book == null)
{
return NotFound();
}
return book;
}
[HttpPost]
public ActionResult<Book> Create(Book book)
{
_bookService.Create(book);
[HttpPut("{id:length(24)}")]
public IActionResult Update(string id, Book bookIn)
{
var book = _bookService.Get(id);
if (book == null)
{
return NotFound();
}
_bookService.Update(id, bookIn);
return NoContent();
return NoContent();
}
[HttpDelete("{id:length(24)}")]
public IActionResult Delete(string id)
{
var book = _bookService.Get(id);
if (book == null)
{
return NotFound();
}
_bookService.Remove(book.Id);
return NoContent();
}
}
}
[
{
"id":"5bfd996f7b8e48dc15ff215d",
"bookName":"Design Patterns",
"price":54.93,
"category":"Computers",
"author":"Ralph Johnson"
},
{
"id":"5bfd996f7b8e48dc15ff215e",
"bookName":"Clean Code",
"price":43.15,
"category":"Computers",
"author":"Robert C. Martin"
}
]
Próximas etapas
Para saber mais sobre a criação de APIs Web do ASP.NET Core, confira os seguintes recursos:
Criar APIs Web com o ASP.NET Core
Tipos de retorno de ação do controlador na API Web ASP.NET Core
Páginas de ajuda da API Web ASP.NET Core com o
Swagger/OpenAPI
13/12/2018 • 4 minutes to read • Edit Online
Todo método de ação pública nos controladores pode ser testado da interface do usuário. Clique em um nome
de método para expandir a seção. Adicione os parâmetros necessários e, em seguida, clique em Experimente!.
NOTE
A versão da interface do usuário do Swagger usada para as capturas de tela é a versão 2. Para obter um exemplo da
versão 3, confira Exemplo Petstore.
Próximas etapas
Introdução ao Swashbuckle
Introdução ao NSwag
Introdução ao Swashbuckle e ao ASP.NET Core
10/01/2019 • 19 minutes to read • Edit Online
Instalação do pacote
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:
Acesse Exibição > Outras Janelas > Console do Gerenciador de Pacotes
Navegue para o diretório no qual o arquivo TodoApi.csproj está localizado
Execute o seguinte comando:
Install-Package Swashbuckle.AspNetCore
using Swashbuckle.AspNetCore.Swagger;
No método Startup.Configure , habilite o middleware para atender ao documento JSON gerado e à interface do
usuário do Swagger:
app.UseMvc();
}
A chamada do método UseSwaggerUI precedente habilita o middleware de arquivos estáticos. Se você estiver
direcionando ao .NET Framework ou ao .NET Core 1.x, adicione o pacote NuGet Microsoft.AspNetCore.StaticFiles
ao projeto.
Inicie o aplicativo e navegue até http://localhost:<port>/swagger/v1/swagger.json . O documento gerado que
descreve os pontos de extremidade é exibido conforme é mostrado na Especificação do Swagger (swagger.json).
A interface do usuário do Swagger pode ser encontrada em http://localhost:<port>/swagger . Explore a API por
meio da interface do usuário do Swagger e incorpore-a em outros programas.
TIP
Para atender à interface do usuário do Swagger na raiz do aplicativo ( http://localhost:<port>/ ), defina a propriedade
RoutePrefix como uma cadeia de caracteres vazia:
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.RoutePrefix = string.Empty;
});
Se estiver usando diretórios com o IIS ou um proxy reverso, defina o ponto de extremidade do Swagger como um
caminho relativo usando o prefixo ./ . Por exemplo, ./swagger/v1/swagger.json . Usar o /swagger/v1/swagger.json
instrui o aplicativo a procurar o arquivo JSON na raiz verdadeira da URL (mais o prefixo da rota, se usado). Por
exemplo, use http://localhost:<port>/<route_prefix>/swagger/v1/swagger.json em vez de
http://localhost:<port>/<virtual_directory>/<route_prefix>/swagger/v1/swagger.json .
Personalizar e estender
O Swagger fornece opções para documentar o modelo de objeto e personalizar a interface do usuário para
corresponder ao seu tema.
Descrição e informações da API
A ação de configuração passada para o método AddSwaggerGen adiciona informações como o autor, a licença e a
descrição:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Propriedades.
Marque a caixa Arquivo de documentação XML na 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 tipos não documentados são indicados por mensagem de aviso. Por exemplo, a
seguinte mensagem indica uma violação do código de aviso 1591:
warning CS1591: Missing XML comment for publicly visible type or member 'TodoController.GetAll()'
Para suprimir os avisos de todo o projeto, defina uma lista separada por ponto e vírgula dos códigos de aviso a
serem ignorados no arquivo do projeto. Acrescentar os códigos de aviso ao $(NoWarn); também aplica os valores
padrão C#.
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
Para suprimir avisos somente para membros específicos, coloque o código nas diretivas de pré-processador
#pragma warning. Essa abordagem é útil para o código que não deve ser exposto por meio dos documentos da
API. No exemplo a seguir, o código de aviso CS1591 é ignorado para toda a classe Program . A imposição do
código de aviso é restaurada no fechamento da definição de classe. Especifique vários códigos de aviso com uma
lista delimitada por vírgulas.
namespace TodoApi
{
#pragma warning disable CS1591
public class Program
{
public static void Main(string[] args) =>
BuildWebHost(args).Run();
Configure o Swagger para usar o arquivo XML gerado. Para sistemas operacionais Linux ou que não sejam
Windows, os caminhos e nomes de arquivo podem diferenciar maiúsculas de minúsculas. Por exemplo, um
arquivo TodoApi.XML é válido no Windows, mas não no CentOS.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetEntryAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}
No código anterior, a Reflexão é usada para criar um nome de arquivo XML correspondente ao do projeto de API
Web. A propriedade AppContext.BaseDirectory é usada para construir um caminho para o arquivo XML.
Adicionar comentários de barra tripla a uma ação aprimora a interface do usuário do Swagger adicionando a
descrição ao cabeçalho da seção. Adicione um elemento <summary> acima da ação Delete :
/// <summary>
/// Deletes a specific TodoItem.
/// </summary>
/// <param name="id"></param>
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
_context.TodoItems.Remove(todo);
_context.SaveChanges();
return NoContent();
}
A interface do usuário do Swagger exibe o texto interno do elemento <summary> do código anterior:
A interface do usuário é controlada pelo esquema JSON gerado:
"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"
}
}
}
_context.TodoItems.Add(item);
_context.SaveChanges();
/// <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(201)]
[ProducesResponseType(400)]
public ActionResult<TodoItem> Create(TodoItem item)
{
_context.TodoItems.Add(item);
_context.SaveChanges();
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
[Required]
public string Name { get; set; }
[DefaultValue(false)]
public bool IsComplete { get; set; }
}
}
A presença desse atributo altera o comportamento da interface do usuário e altera o esquema JSON subjacente:
"definitions": {
"TodoItem": {
"required": [
"name"
],
"type": "object",
"properties": {
"id": {
"format": "int64",
"type": "integer"
},
"name": {
"type": "string"
},
"isComplete": {
"default": false,
"type": "boolean"
}
}
}
},
Adicione o atributo [Produces("application/json")] ao controlador da API. Sua finalidade é declarar que as ações
do controlador permitem o tipo de conteúdo de resposta application/json:
[Produces("application/json")]
[Route("api/[controller]")]
public class TodoController : ControllerBase
{
private readonly TodoContext _context;
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class TodoController : ControllerBase
{
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.
Descrever os tipos de resposta
Os desenvolvedores que usam uma API Web estão mais preocupados com o que é retornado—, especificamente,
os tipos de resposta e os códigos de erro (se eles não forem padrão). Os tipos de resposta e os códigos de erro
são indicados nos comentários XML e nas anotações de dados.
A ação Create retorna um código de status HTTP 201 em caso de sucesso. Um código de status HTTP 400 é
retornado 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. Corrija esse problema
adicionando as linhas realçadas 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(400)]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
_context.TodoItems.Add(item);
_context.SaveChanges();
/// <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(201)]
[ProducesResponseType(400)]
public ActionResult<TodoItem> Create(TodoItem item)
{
_context.TodoItems.Add(item);
_context.SaveChanges();
A interface do usuário do Swagger agora documenta claramente os códigos de resposta HTTP esperados:
No ASP.NET Core 2.2 ou posterior, as convenções podem ser usadas como uma alternativa para decorar
explicitamente as ações individuais com [ProducesResponseType] . Para obter mais informações, consulte Usar
convenções de API Web.
Personalizar a interface do usuário
A interface do usuário de estoque é apresentável e funcional. No entanto, as páginas de documentação da API
devem representar a sua marca ou o seu tema. Para inserir sua marca nos componentes do Swashbuckle é
necessário adicionar recursos para atender aos arquivos estáticos e criar a estrutura de pasta para hospedar esses
arquivos.
Se você estiver direcionando ao .NET Framework ou ao .NET Core 1.x, adicione o pacote do NuGet
Microsoft.AspNetCore.StaticFiles ao projeto:
O pacote do NuGet anterior já estará instalado se você estiver direcionando ao .NET Core 2.x e usando o
metapackage.
Habilitar o middleware de arquivos estáticos:
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.
Crie um arquivo custom.css em swagger/wwwroot/ui, com o seguinte CSS para personalizar o cabeçalho da
página:
.swagger-ui .topbar {
background-color: #000;
border-bottom: 3px solid #547f00;
}
<link href="https://fonts.googleapis.com/css?
family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
<link rel="stylesheet" type="text/css" href="custom.css">
Install-Package NSwag.AspNetCore
using NJsonSchema;
using NSwag.AspNetCore;
No método Configure , habilite o middleware para atender à especificação do Swagger gerado e à interface do
usuário do Swagger:
app.UseMvc();
}
Geração de código
Você pode tirar proveito dos recursos de geração de código do NSwag, escolhendo uma das opções a seguir:
NSwagStudio – um aplicativo da área de trabalho do Windows para geração do código do cliente da API em C#
ou TypeScript.
Pacotes NuGet NSwag.CodeGeneration.CSharp ou NSwag.CodeGeneration.TypeScript para a geração de
código dentro do seu projeto.
NSwag na linha de comando.
O pacote NuGet NSwag.MSBuild.
Gerar o código com NSwagStudio
Instale o NSwagStudio, seguindo as instruções no repositório GitHub do NSwagStudio.
Inicie o NSwagStudio e insira a URL do arquivo swagger.json na caixa de texto URL de Especificação do
Swagger. Por exemplo, http://localhost:44354/swagger/v1/swagger.json.
Clique no botão Criar Cópia local para gerar uma representação JSON de sua especificação do Swagger.
Na área Saídas, marque a caixa de seleção Cliente CSharp. Dependendo do seu projeto, você também
pode escolher Cliente TypeScript ou Controlador da API Web CSharp. Se você selecionar Controlador
da API Web do CSharp, uma especificação de serviço recompilará o serviço, que servirá como uma
geração inversa.
Clique em Gerar Saídas para produzir uma implementação completa de cliente em C# do projeto
TodoApi.NSwag. Para ver o código de cliente gerado, clique na guia Cliente do CSharp:
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v12.0.9.0 (NJsonSchema v9.13.10.0 (Newtonsoft.Json v11.0.0.0))
(http://NSwag.org)
// </auto-generated>
//----------------------
namespace MyNamespace
{
#pragma warning disable
TIP
O código do cliente em C# é gerado com base em seleções na guia Configurações. Modifique as configurações para executar
tarefas como geração de método síncrono e renomeação de namespace padrão.
services.AddSwaggerDocument(config =>
{
config.PostProcess = document =>
{
document.Info.Version = "v1";
document.Info.Title = "ToDo API";
document.Info.Description = "A simple ASP.NET Core web API";
document.Info.TermsOfService = "None";
document.Info.Contact = new NSwag.SwaggerContact
{
Name = "Shayne Boyer",
Email = string.Empty,
Url = "https://twitter.com/spboyer"
};
document.Info.License = new NSwag.SwaggerLicense
{
Name = "Use under LICX",
Url = "https://example.com/license"
};
};
});
comentários XML
Para habilitar os comentários XML, execute as seguintes etapas:
Visual Studio
Visual Studio para Mac
Visual Studio Code
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Editar
<nome_do_projeto>.csproj.
Manualmente, adicione as linhas destacadas ao arquivo .csproj:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Propriedades
Marque a caixa Arquivo de documentação XML na seção Saída da guia Build
Anotações de dados
Como o NSwag usa Reflexão, e o tipo recomendado de retorno para ações da API Web é IActionResult, ele não
consegue inferir o que sua ação está fazendo e o que ela retorna.
Considere o exemplo a seguir:
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
_context.TodoItems.Add(item);
_context.SaveChanges();
A ação anterior retorna IActionResult , mas dentro da ação, ela está retornando CreatedAtRoute ou BadRequest.
Use anotações de dados para informar aos clientes quais códigos de status HTTP esta ação costuma retornar.
Decore a ação com os seguintes atributos:
Como o NSwag usa Reflection, e o tipo de retorno recomendado para as ações da API Web é ActionResult<T>, ele
só consegue inferir o tipo de retorno definido por T . Não é possível inferir automaticamente outros tipos de
retorno possíveis.
Considere o exemplo a seguir:
[HttpPost]
public ActionResult<TodoItem> Create(TodoItem item)
{
_context.TodoItems.Add(item);
_context.SaveChanges();
A ação anterior retorna ActionResult<T> . Dentro da ação, ela está retornando CreatedAtRoute. Como o
controlador está decorado com o atributo [ApiController], uma resposta BadRequest também é possível. Para obter
mais informações, veja Respostas automáticas HTTP 400. Use anotações de dados para informar aos clientes quais
códigos de status HTTP esta ação costuma retornar. Decore a ação com os seguintes atributos:
[ProducesResponseType(201)] // Created
[ProducesResponseType(400)] // BadRequest
No ASP.NET Core 2.2 ou posterior, é possível usar as convenções em vez de decorar explicitamente as ações
individuais com [ProducesResponseType] . Para obter mais informações, consulte Usar convenções de API Web.
O gerador do Swagger agora pode descrever essa ação precisamente e os clientes gerados conseguem saber o que
recebem ao chamar o ponto de extremidade. Recomendamos decorar todas as ações com esses atributos.
Para obter diretrizes sobre quais respostas HTTP as ações da API devem retornar, confira a Especificação RFC
7231.
Tipos de retorno de ação do controlador na API
Web ASP.NET Core
16/01/2019 • 9 minutes to read • Edit Online
Tipo específico
A ação mais simples retorna um tipo de dados complexo ou primitivo (por exemplo, string ou um tipo de objeto
personalizado). Considere a seguinte ação, que retorna uma coleção de objetos Product personalizados:
[HttpGet]
public IEnumerable<Product> Get()
{
return _repository.GetProducts();
}
Sem condições conhecidas contra as quais se proteger durante a execução da ação, retornar um tipo específico
pode ser suficiente. A ação anterior não aceita parâmetros, assim, validação de restrições de parâmetro não é
necessária.
Quando condições conhecidas precisarem ser incluídas em uma ação, vários caminhos de retorno serão
introduzidos. Nesse caso, é comum combinar um tipo de retorno ActionResult com o tipo de retorno primitivo ou
complexo. IActionResult ou ActionResult<T > é necessário para acomodar esse tipo de ação.
Tipo IActionResult
O tipo de retorno IActionResult é apropriado quando vários tipos de retorno ActionResult são possíveis em uma
ação. Os tipos ActionResult representam vários códigos de status HTTP. Alguns tipos de retorno comuns que se
enquadram nessa categoria são BadRequestResult (400) NotFoundResult (404) e OkObjectResult (200).
Porque há vários tipos de retorno e caminhos na ação, o uso liberal do atributo [ProducesResponseType] é
necessário. Esse atributo produz detalhes de resposta mais descritivos para páginas de ajuda da API geradas por
ferramentas como Swagger. [ProducesResponseType] indica os tipos conhecidos e os códigos de status HTTP a
serem retornados pela ação.
Ação síncrona
Considere a seguinte ação síncrona em que há dois tipos de retorno possíveis:
[HttpGet("{id}")]
[ProducesResponseType(200, Type = typeof(Product))]
[ProducesResponseType(404)]
public IActionResult GetById(int id)
{
if (!_repository.TryGetProduct(id, out var product))
{
return NotFound();
}
return Ok(product);
}
Na ação anterior, um código de status 404 é retornado quando o produto representado por id não existe no
armazenamento de dados subjacente. O método auxiliar NotFound é invocado como um atalho para
return new NotFoundResult(); . Se o produto existir, um objeto Product que representa o conteúdo será
retornado com um código de status 200. O método auxiliar OK é invocado como a forma abreviada de
return new OkObjectResult(product); .
Ação assíncrona
Considere a seguinte ação assíncrona em que há dois tipos de retorno possíveis:
[HttpPost]
[ProducesResponseType(201, Type = typeof(Product))]
[ProducesResponseType(400)]
public async Task<IActionResult> CreateAsync([FromBody] Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
await _repository.AddProductAsync(product);
No código anterior:
O tempo de execução do ASP.NET Core retorna um código de status 400 (BadRequest) quando a descrição
do produto contém "Widget XYZ".
O método CreatedAtAction gera um código de status 201 quando um produto é criado. Nesse caminho de
código, o objeto Product é retornado.
Por exemplo, o modelo a seguir indica que as solicitações devem incluir as propriedades Name e Description .
Portanto, a falha em fornecer Name e Description na solicitação causa falha na validação do modelo.
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
}
Se o atributo [ApiController] no ASP.NET Core 2.1 ou posterior for aplicado, erros de validação de modelo
resultarão em um código de status 400. Para obter mais informações, veja Respostas automáticas HTTP 400.
Tipo ActionResult<T>
O ASP.NET Core 2.1 apresenta o tipo de retorno ActionResult<T > para ações do controlador de API Web.
Permite que você retorne um tipo derivado de ActionResult ou retorne um tipo específico. ActionResult<T>
oferece os seguintes benefícios em relação ao tipo IActionResult:
O atributo [ProducesResponseType] pode ter sua propriedade Type excluída. Por exemplo,
[ProducesResponseType(200, Type = typeof(Product))] é simplificado para [ProducesResponseType(200)] . O tipo
de retorno esperado da ação é inferido do T em ActionResult<T> .
Operadores de conversão implícita são compatíveis com a conversão de T e ActionResult em
ActionResult<T> . T converte em ObjectResult, o que significa que return new ObjectResult(T); é
simplificado para return T; .
C# não dá suporte a operadores de conversão implícita em interfaces. Consequentemente, a conversão da
interface para um tipo concreto é necessário para usar ActionResult<T> . Por exemplo, o uso de IEnumerable no
exemplo a seguir não funciona:
[HttpGet]
public ActionResult<IEnumerable<Product>> Get()
{
return _repository.GetProducts();
}
[HttpGet("{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public ActionResult<Product> GetById(int id)
{
if (!_repository.TryGetProduct(id, out var product))
{
return NotFound();
}
return product;
}
No código anterior, um código de status 404 é retornado quando o produto não existe no banco de dados. Se o
produto existir, o objeto Product correspondente será retornado. Antes do ASP.NET Core 2.1, a linha
return product; teria sido return Ok(product); .
TIP
Do ASP.NET Core 2.1 em diante, a inferência de origem de associação de parâmetro de ação é habilitada quando uma classe
de controlador é decorada com o atributo [ApiController] . Um nome de parâmetro correspondente a um nome do
modelo de rota é associado automaticamente usando os dados de rota de solicitação. Consequentemente, o parâmetro
id da ação anterior não é explicitamente anotado com o atributo [FromRoute].
Ação assíncrona
Considere uma ação assíncrona em que há dois tipos de retorno possíveis:
[HttpPost]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
await _repository.AddProductAsync(product);
No código anterior:
O tempo de execução do ASP.NET Core retorna um código de status 400 (BadRequest) quando:
O atributo [ApiController] tiver sido aplicado e o modelo de validação falhar.
A descrição do produto contém "Widget XYZ".
O método CreatedAtAction gera um código de status 201 quando um produto é criado. Nesse caminho de
código, o objeto Product é retornado.
TIP
Do ASP.NET Core 2.1 em diante, a inferência de origem de associação de parâmetro de ação é habilitada quando uma classe
de controlador é decorada com o atributo [ApiController] . Parâmetros de tipo complexo são vinculados
automaticamente usando o corpo da solicitação. Consequentemente, o parâmetro product da ação anterior não é
explicitamente anotado com o atributo [FromBody].
Recursos adicionais
Tratar solicitações com controladores no ASP.NET Core MVC
Validação de modelo no ASP.NET Core MVC
Páginas de ajuda da API Web ASP.NET Core com o Swagger/OpenAPI
Formatar dados de resposta na API Web ASP.NET
Core
30/10/2018 • 16 minutes to read • Edit Online
NOTE
Uma ação não precisa retornar nenhum tipo específico; o MVC dá suporte a qualquer valor retornado de objeto. Se uma
ação retorna uma implementação IActionResult e o controlador herda de Controller , os desenvolvedores têm
muitos métodos auxiliares correspondentes a muitas das opções. Os resultados de ações que retornam objetos que não
são tipos IActionResult serão serializados usando a implementação IOutputFormatter apropriada.
Para retornar dados em um formato específico de um controlador que herda da classe base Controller , use o
método auxiliar interno Json para retornar JSON e Content para texto sem formatação. O método de ação
deve retornar o tipo de resultado específico (por exemplo, JsonResult ) ou IActionResult .
Retornando dados formatados em JSON:
// GET: api/authors
[HttpGet]
public JsonResult Get()
{
return Json(_authorRepository.List());
}
// GET api/authors/about
[HttpGet("About")]
public ContentResult About()
{
return Content("An API listing authors of docs.asp.net.");
}
// GET api/authors/version
[HttpGet("version")]
public string Version()
{
return "Version 1.0.0";
}
TIP
Para ações não triviais com vários tipos de retorno ou várias opções (por exemplo, códigos de status HTTP diferentes com
base no resultado das operações executadas), prefira IActionResult como o tipo de retorno.
Negociação de conteúdo
A negociação de conteúdo (conneg de forma abreviada) ocorre quando o cliente especifica um cabeçalho Accept.
O formato padrão usado pelo ASP.NET Core MVC é JSON. A negociação de conteúdo é implementada por
ObjectResult . Ela também foi desenvolvida com base nos resultados de ação específica de código de status
retornados dos métodos auxiliares (que se baseiam em ObjectResult ). Também retorne um tipo de modelo (uma
classe que você definiu como o tipo de transferência de dados) e a estrutura o encapsulará automaticamente em
um ObjectResult para você.
O seguinte método de ação usa os métodos auxiliares Ok e NotFound :
// GET: api/authors/search?namelike=th
[HttpGet("Search")]
public IActionResult Search(string namelike)
{
var result = _authorRepository.GetByNameSubstring(namelike);
if (!result.Any())
{
return NotFound(namelike);
}
return Ok(result);
}
Uma resposta formatada em JSON será retornada, a menos que outro formato tenha sido solicitado e o servidor
possa retornar o formato solicitado. Use uma ferramenta como o Fiddler para criar uma solicitação que inclui um
cabeçalho Accept e especifique outro formato. Nesse caso, se o servidor tiver um formatador que pode produzir
uma resposta no formato solicitado, o resultado será retornado no formato preferencial do cliente.
Na captura de tela acima, o Fiddler Composer foi usado para gerar uma solicitação, especificando
Accept: application/xml . Por padrão, o ASP.NET Core MVC dá suporte apenas ao JSON e, portanto, mesmo
quando outro formato é especificado, o resultado retornado ainda é formatado em JSON. Você verá como
adicionar outros formatadores na próxima seção.
As ações do controlador podem retornar POCOs (Objetos CLR Básicos); nesse caso, o ASP.NET Core MVC
criará automaticamente um ObjectResult que encapsula o objeto. O cliente receberá o objeto serializado
formatado (o formato JSON é o padrão; você pode configurar XML ou outros formatos). Se o objeto que está
sendo retornado for null , a estrutura retornará uma resposta 204 No Content .
Retornando um tipo de objeto:
// GET api/authors/ardalis
[HttpGet("{alias}")]
public Author Get(string alias)
{
return _authorRepository.GetByAlias(alias);
}
Na amostra, uma solicitação para um alias de autor válido receberá uma resposta 200 OK com os dados do
autor. Uma solicitação para um alias inválido receberá uma resposta 204 Sem Conteúdo. Capturas de tela
mostrando a resposta nos formatos XML e JSON são mostradas abaixo.
Processo de negociação de conteúdo
A negociação de conteúdo ocorre apenas se um cabeçalho Accept é exibido na solicitação. Quando uma
solicitação contiver um cabeçalho Accept, a estrutura enumerará os tipos de mídia no cabeçalho Accept na ordem
de preferência e tentará encontrar um formatador que pode produzir uma resposta em um dos formatos
especificados pelo cabeçalho Accept. Caso nenhum formatador que pode atender à solicitação do cliente seja
encontrado, a estrutura tentará encontrar o primeiro formatador que pode produzir uma resposta (a menos que
o desenvolvedor tenha configurado a opção em MvcOptions para retornar 406 Não Aceitável). Se a solicitação
especificar XML, mas o formatador XML não tiver sido configurado, o formatador JSON será usado. Geralmente,
se nenhum formatador que pode fornecer o formato solicitado for configurado, o primeiro formatador que pode
formatar o objeto será usado. Se nenhum cabeçalho for fornecido, o primeiro formatador que pode manipular o
objeto a ser retornado será usado para serializar a resposta. Nesse caso, não ocorre nenhuma negociação – o
servidor determina qual formato será usado.
NOTE
Se o cabeçalho Accept contiver */* , o Cabeçalho será ignorado, a menos que RespectBrowserAcceptHeader seja
definido como verdadeiro em MvcOptions .
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true; // false by default
});
Configurando formatadores
Se o aplicativo precisar dar suporte a outros formatos além do padrão de JSON, adicione pacotes NuGet e
configure o MVC para dar suporte a eles. Há formatadores separados para entrada e saída. Os formatadores de
entrada são usados pelo Model Binding; os formatadores de saída são usados para formatar as respostas.
Também configure Formatadores Personalizados.
Adicionando suporte para o formato XML
Para adicionar suporte para a formatação XML, instale o pacote NuGet Microsoft.AspNetCore.Mvc.Formatters.Xml .
Adicione os XmlSerializerFormatters à configuração do MVC em Startup.cs:
services.AddMvc()
.AddXmlSerializerFormatters();
services.AddMvc(options =>
{
options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});
Depois de adicionar suporte para a formatação XML, os métodos do controlador deverão retornar o formato
apropriado com base no cabeçalho Accept da solicitação, como demonstra este exemplo do Fiddler:
Na guia Inspetores, é possível ver que a solicitação GET Bruta foi feita com um conjunto de cabeçalhos
Accept: application/xml . O painel de resposta mostra o cabeçalho Content-Type: application/xml e o objeto
Author foi serializado em XML.
Use a guia Criador para modificar a solicitação para especificar application/json no cabeçalho Accept . Execute
a solicitação e a resposta será formatada como JSON:
Nessa captura de tela, é possível ver a solicitação definir um cabeçalho Accept: application/json e a resposta
especificar o mesmo como seu Content-Type . O objeto Author é mostrado no corpo da resposta, em formato
JSON.
Forçando um formato específico
Caso deseje restringir os formatos de resposta de uma ação específica, aplique o filtro [Produces] . O filtro
[Produces] especifica os formatos de resposta de uma ação específica (ou o controlador ). Como a maioria dos
Filtros, isso pode ser aplicado no escopo da ação, do controlador ou global.
[Produces("application/json")]
public class AuthorsController
O filtro [Produces] forçará todas as ações em AuthorsController a retornar respostas formatadas em JSON,
mesmo se outros formatadores foram configurados para o aplicativo e o cliente forneceu um cabeçalho Accept
solicitando outro formato disponível. Consulte Filtros para saber mais, incluindo como aplicar filtros
globalmente.
Formatadores de casos especiais
Alguns casos especiais são implementados com formatadores internos. Por padrão, os tipos de retorno string
serão formatados como text/plain (text/html, se solicitado por meio do cabeçalho Accept ). Esse comportamento
pode ser removido com a remoção do TextOutputFormatter . Remova formatadores no método Configure em
Startup.cs (mostrado abaixo). Ações que têm um tipo de retorno de objeto de modelo retornarão uma resposta
204 Sem Conteúdo ao retornar null . Esse comportamento pode ser removido com a remoção do
HttpNoContentOutputFormatter . O código a seguir remove o TextOutputFormatter e o
HttpNoContentOutputFormatter .
services.AddMvc(options =>
{
options.OutputFormatters.RemoveType<TextOutputFormatter>();
options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
});
Sem o TextOutputFormatter , os tipos de retorno string retornam 406 Não Aceitável, por exemplo. Observe que
se um formatador XML existir, ele formatará tipos de retorno string se o TextOutputFormatter for removido.
Sem o HttpNoContentOutputFormatter , os objetos nulos são formatados com o formatador configurado. Por
exemplo, o formatador JSON apenas retornará uma resposta com um corpo null , enquanto o formatador XML
retornará um elemento XML vazio com o atributo xsi:nil="true" definido.
[FormatFilter]
public class ProductsController
{
[Route("[controller]/[action]/{id}.{format?}")]
public Product GetById(int id)
Essa rota permitirá que o formato solicitado seja especificado como uma extensão de arquivo opcional. O
atributo [FormatFilter] verifica a existência do valor de formato no RouteData e mapeará o formato da resposta
para o formatador adequado quando a resposta for criada.
ROTA FORMATADOR
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
NOTE
Não é possível fazer a injeção de dependência de construtor em uma classe de formatador. Por exemplo, não é possível
obter um agente adicionando um parâmetro de agente ao construtor. Para acessar serviços, você precisa usar o objeto de
contexto que é passado para seus métodos. O exemplo de código abaixo mostra como isso é feito.
Substituir CanReadType/CanWriteType
Especifique o tipo no qual desserializar ou do qual serializar substituindo os métodos CanReadType ou
CanWriteType . Por exemplo, talvez você só possa criar texto de vCard de um tipo Contact e vice-versa.
Substituir ReadRequestBodyAsync/WriteResponseBodyAsync
Você faz o trabalho real de desserialização ou serialização em ReadRequestBodyAsync ou WriteResponseBodyAsync .
As linhas destacadas no exemplo a seguir mostram como obter serviços do contêiner de injeção de dependência
(não é possível obtê-los dos parâmetros do construtor).
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
IServiceProvider serviceProvider = context.HttpContext.RequestServices;
var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger;
services.AddMvc(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
Formatadores são avaliados na ordem em que você os insere. O primeiro deles tem precedência.
Próximas etapas
Código de exemplo do formatador de texto sem formatação no GitHub.
Aplicativo de exemplo para este documento, que implementa formatadores de entrada e saída simples de
vCard. O aplicativo lê e grava vCards parecidos com o exemplo a seguir:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
UID:20293482-9240-4d68-b475-325df4a83728
END:VCARD
Para ver a saída do vCard, execute o aplicativo e envie uma solicitação Get com o cabeçalho de aceitação
"texto/vcard" para http://localhost:63313/api/contacts/ (ao executar com Visual Studio) ou
http://localhost:5000/api/contacts/ (ao executar da linha de comando).
Para adicionar um vCard à coleção de contatos na memória, envie uma solicitação Post para a mesma URL, com
cabeçalho Content-Type "texto/vcard" e com o texto do vCard no corpo, formatado como o exemplo acima.
Usar os analisadores da API Web
10/01/2019 • 3 minutes to read • Edit Online
Instalação do pacote
Microsoft.AspNetCore.Mvc.Api.Analyzers pode ser adicionado com uma das seguintes abordagens:
Visual Studio
Visual Studio para Mac
Visual Studio Code
CLI do .NET Core
Da janela Console do Gerenciador de Pacotes:
Acesse Exibição > Outras Janelas > Console do Gerenciador de Pacotes.
Navegue até o diretório no qual o arquivo ApiConventions.csproj está localizado.
Execute o seguinte comando:
Install-Package Microsoft.AspNetCore.Mvc.Api.Analyzers
if (contact == null)
{
return NotFound();
}
return Ok(contact);
}
A ação precedente documenta o tipo de retorno com êxito do HTTP 200, mas não documenta o código de status
com falha do HTTP 404. O analisador relata a documentação ausente para o código de status HTTP 404 como um
aviso. É fornecida uma opção para consertar o problema.
Recursos adicionais
Usar convenções de API Web
Páginas de ajuda da API Web ASP.NET Core com o Swagger/OpenAPI
Anotação com o atributo ApiController
Usar convenções de API Web
06/02/2019 • 6 minutes to read • Edit Online
if (contactToUpdate == null)
{
return NotFound();
}
_contacts.Update(contact);
return NoContent();
}
[ProducesDefaultResponseType]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
[ProducesResponseType(400)]
[ApiController]
[ApiConventionType(typeof(DefaultApiConventions))]
[Route("api/[controller]")]
public class ContactsConventionController : ControllerBase
{
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
namespace ApiConventions
{
public class Startup
{
Se atributos de metadados mais específicos estão ausentes, a aplicação dessa convenção a um assembly impõe
que:
O método de convenção se aplica a qualquer ação denominada Find .
Um parâmetro denominado id está presente na ação Find .
Requisitos de nomenclatura
Os atributos [ApiConventionNameMatch] e [ApiConventionTypeMatch] podem ser aplicados ao método de
convenção que determina as ações às quais eles são aplicáveis. Por exemplo:
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
public static void Find(
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
int id)
{ }
No exemplo anterior:
A opção Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionNameMatchBehavior.Prefix aplicada ao método
indica que a convenção corresponde a qualquer ação desde que seja prefixada com "Find". Exemplos de ações
correspondentes incluem Find , FindPet e FindById .
O Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionNameMatchBehavior.Suffix aplicado ao parâmetro indica
que a convenção corresponde a métodos com exatamente um parâmetro encerrando no identificador do
sufixo. Exemplos incluem parâmetros como id ou petId . ApiConventionTypeMatch pode ser aplicado da
mesma forma aos tipos para restringir o tipo do parâmetro. Um argumento params[] indica parâmetros
restantes que não precisam ser explicitamente correspondidos.
Recursos adicionais
Usar os analisadores da API Web
Páginas de ajuda da API Web ASP.NET Core com o Swagger/OpenAPI
Introdução ao SignalR do ASP.NET Core
24/01/2019 • 4 minutes to read • Edit Online
O que é o SignalR?
SignalR do ASP.NET Core é uma biblioteca de software livre que simplifica a adição da funcionalidade da web
em tempo real aos aplicativos. A funcionalidade da web em tempo real permite que o código do lado do servidor
para conteúdo de envio por push aos clientes instantaneamente.
Bons candidatos para o SignalR:
Aplicativos que exigem atualizações de alta frequência do servidor. Exemplos são jogos, redes sociais, de
votação, leilão, mapas e aplicativos GPS.
Os painéis 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 ). As
RPCs chamam funções JavaScript em clientes do código do lado do servidor do .NET Core.
Aqui estão alguns recursos do SignalR para ASP.NET Core:
Lida com gerenciamento de conexão automaticamente.
Envia mensagens para todos os clientes conectados simultaneamente. Por exemplo, uma sala de bate-papo.
Envia mensagens para clientes específicos ou grupos de clientes.
É dimensionada para lidar com o aumento de tráfego.
A fonte é hospedada em um SignalR repositório no GitHub.
Transportes
O SignalR dá suporte a várias técnicas para manipular as comunicações em tempo real:
WebSockets
Eventos enviados pelo servidor
Sondagem longa
O SignalR escolhe automaticamente o melhor método de transporte que está dentro de recursos do servidor e
cliente.
Hubs
Usa o SignalR hubs para comunicação entre clientes e servidores.
Um hub é um pipeline de alto nível que permite que um cliente e servidor chamar métodos em si. O SignalR
manipula a expedição entre limites de máquina automaticamente, permitindo que os clientes chamar métodos no
servidor e vice-versa. Você pode passar parâmetros fortemente tipados para métodos, que habilita 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 em
comparação comparadas JSON. Devem dar suporte a navegadores mais antigos nível XHR 2 para fornecer
suporte de protocolo MessagePack.
Os hubs de chamar o código do lado do cliente enviando mensagens que contêm o nome e parâmetros do
método do lado do cliente. Os objetos enviados como parâmetros de método são desserializados usando o
protocolo configurado. O cliente tenta corresponder o nome a um método no código do lado do cliente. Quando
o cliente encontra uma correspondência, ele chama o método e passa a ele os dados de parâmetro desserializado.
Recursos adicionais
Introdução ao SignalR para ASP.NET Core
Plataformas com suporte
Hubs
Cliente JavaScript
Plataformas com suporte do SignalR do ASP.NET
Core
25/01/2019 • 2 minutes to read • Edit Online
Cliente JavaScript
O cliente JavaScript é executado no NodeJS 8 e versões posteriores e os seguintes navegadores:
NAVEGADOR VERSÃO
Cliente .NET
O cliente .NET é executado em qualquer plataforma com suporte pelo ASP.NET Core. Por exemplo,
desenvolvedores Xamarin podem usar o SignalR para a criação de aplicativos Android usando o xamarin. Android
8.4.0.1 e posterior e aplicativos iOS usando xamarin. IOS 11.14.0.4 e versões posteriores.
Se o servidor executa o IIS, o transporte de WebSockets requer o IIS 8.0 ou superior no Windows Server 2012 ou
superior. Outros transportes têm suporte em todas as plataformas.
Cliente de Java
O cliente Java dá suporte a Java 8 e versões posteriores.
Este tutorial ensina as noções básicas da criação de um aplicativo em tempo real usando o SignalR. Você
aprenderá como:
Crie um projeto Web.
Adicionar uma biblioteca de clientes do SignalR.
Criar um hub do SignalR.
Configurar o projeto para usar o SignalR.
Adicione o código que envia mensagens de qualquer cliente para todos os clientes conectados.
No final, você terá um aplicativo de chat funcionando:
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
SDK 2.2 ou posterior do .NET Core
Selecione Aplicativo Web para criar um projeto que usa Razor Pages.
Selecione uma estrutura de destino do .NET Core, selecione ASP.NET Core 2.2 e clique em OK.
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
A classe ChatHub é herda da classe Hub do SignalR. A classe Hub gerencia conexões, grupos e sistemas
de mensagens.
O método SendMessage pode ser chamado por um cliente conectado para enviar uma mensagem a todos
os clientes. O código cliente do JavaScript que chama o método é mostrado posteriormente no tutorial. O
código do SignalR é assíncrono para fornecer o máximo de escalabilidade.
Configurar o SignalR
O servidor do SignalR precisa ser configurado para passar solicitações do SignalR ao SignalR.
Adicione o seguinte código realçado ao arquivo Startup.cs.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SignalRChat.Hubs;
namespace SignalRChat
{
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)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for
a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
}
// 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();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
app.UseMvc();
}
}
}
@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/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>
O código anterior:
Cria as caixas de texto para o nome e a mensagem de texto e um botão Enviar.
Cria uma lista com id="messagesList" para exibir as mensagens recebidas do hub do SignalR.
Inclui referências de script ao SignalR e ao código do aplicativo chat.js que você criará na próxima
etapa.
Na pasta wwwroot/js, crie um arquivo chat.js com o código a seguir:
"use strict";
connection.start().then(function(){
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
O código anterior:
Cria e inicia uma conexão.
Adiciona no botão Enviar um manipulador que envia mensagens ao hub.
Adiciona no objeto de conexão um manipulador que recebe mensagens do hub e as adiciona à lista.
Executar o aplicativo
Visual Studio
Visual Studio Code
Visual Studio para Mac
Pressione CTRL + F5 para executar o aplicativo sem depuração.
Copie a URL da barra de endereços, abra outra instância ou guia do navegador e cole a URL na barra de
endereços.
Escolha qualquer navegador, insira um nome e uma mensagem e selecione o botão Enviar Mensagem.
O nome e a mensagem são exibidos em ambas as páginas instantaneamente.
TIP
Se o aplicativo não funcionar, abra as ferramentas para desenvolvedores do navegador (F12) e acesse o console. Você pode
encontrar erros relacionados ao código HTML e JavaScript. Por exemplo, suponha que você coloque signalr.js em uma
pasta diferente daquela direcionada. Nesse caso, a referência a esse arquivo não funcionará e ocorrerá um erro 404 no
console.
Próximas etapas
Neste tutorial, você aprendeu como:
Criar um projeto de aplicativo Web.
Adicionar uma biblioteca de clientes do SignalR.
Criar um hub do SignalR.
Configurar o projeto para usar o SignalR.
Adicionar o código que usa o hub para enviar mensagens de qualquer cliente para todos os clientes
conectados.
Para saber mais sobre o SignalR, confira a introdução:
Introdução ao ASP.NET Core SignalR
Usar o SignalR do ASP.NET Core com TypeScript e
Webpack
28/01/2019 • 19 minutes to read • Edit Online
Prerequisites
Visual Studio
Visual Studio Code
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
SDK 2.2 ou posterior do .NET Core
npm init -y
{
"name": "SignalRWebPack",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Definir a propriedade private como true evita avisos de instalação de pacote na próxima etapa.
3. Instalar os pacotes de npm exigidos. Execute o seguinte comando na raiz do projeto:
npm install -D -E [email protected] [email protected] [email protected] mini-css-
[email protected] [email protected] [email protected] [email protected] [email protected]
"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},
module.exports = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "wwwroot"),
filename: "[name].[chunkhash].js",
publicPath: "/"
},
resolve: {
extensions: [".js", ".ts"]
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
new CleanWebpackPlugin(["wwwroot/*"]),
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
new MiniCssExtractPlugin({
filename: "css/[name].[chunkhash].css"
})
]
};
*, *::before, *::after {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
}
.input-zone {
align-items: center;
display: flex;
flex-direction: row;
margin: 10px;
}
.input-zone-input {
flex: 1;
margin-right: 10px;
}
.message-author {
font-weight: bold;
}
.messages {
border: 1px solid #000;
margin: 10px;
max-height: 300px;
min-height: 300px;
overflow-y: auto;
padding: 5px;
}
O código precedente configura o compilador TypeScript para produzir um JavaScript compatível com
ECMAScript 5.
11. Crie src/index.ts com o seguinte conteúdo:
import "./css/main.css";
btnSend.addEventListener("click", send);
function send() {
}
O TypeScript precedente recupera referências a elementos DOM e anexa dois manipuladores de eventos:
keyup : esse evento é acionado quando o usuário digita algo na caixa de texto identificada como
tbMessage . A função send é chamada quando o usuário pressionar a tecla Enter.
click : esse evento é acionado quando o usuário clica no botão Enviar. A função send é chamada.
app.UseDefaultFiles();
app.UseStaticFiles();
O código precedente permite que o servidor localize e forneça o arquivo index.html, se o usuário inserir a
URL completa ou a URL raiz do aplicativo Web.
2. Chame AddSignalR no método Startup.ConfigureServices . Adiciona serviços SignalR ao seu projeto.
services.AddSignalR();
3. Mapeie uma rota /hub para o hub ChatHub . Adicione as linhas a seguir ao final do método
Startup.Configure :
app.UseSignalR(options =>
{
options.MapHub<ChatHub>("/hub");
});
4. Crie um novo diretório, chamado Hubs, na raiz do projeto. A finalidade é armazenar o hub SignalR, que é
criado na próxima etapa.
5. Crie o hub Hubs/ChatHub.cs com o código a seguir:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
}
}
6. Adicione o código a seguir ao topo do arquivo Startup.cs para resolver a referência ChatHub :
using SignalRWebPack.Hubs;
O comando precedente instala o cliente TypeScript do SignalR, que permite ao cliente enviar mensagens
para o servidor.
2. Adicione o código destacado ao arquivo src/index.ts:
import "./css/main.css";
import * as signalR from "@aspnet/signalr";
m.innerHTML =
`<div class="message-author">${username}</div><div>${message}</div>`;
divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});
btnSend.addEventListener("click", send);
function send() {
}
messageContainer.innerHTML =
`<div class="message-author">${username}</div><div>${message}</div>`;
divMessages.appendChild(messageContainer);
divMessages.scrollTop = divMessages.scrollHeight;
});
btnSend.addEventListener("click", send);
function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => tbMessage.value = "");
}
Enviar uma mensagem por meio da conexão WebSockets exige uma chamada para o método send . O
primeiro parâmetro do método é o nome da mensagem. Os dados da mensagem residem nos outros
parâmetros. Neste exemplo, uma mensagem identificada como newMessage é enviada ao servidor. A
mensagem é composta do nome de usuário e da entrada em uma caixa de texto. Se o envio for bem-
sucedido, o valor da caixa de texto será limpo.
4. Adicione o método em destaque à classe ChatHub :
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
public async Task NewMessage(string username, string message)
{
await Clients.All.SendAsync("messageReceived", username, message);
}
}
}
O código precedente transmite as mensagens recebidas para todos os usuários conectados quando o
servidor as recebe. Não é necessário ter um método genérico on para receber todas as mensagens. Um
método nomeado com o nome da mensagem é suficiente.
Neste exemplo, o cliente TypeScript envia uma mensagem identificada como newMessage . O método
NewMessage de C# espera os dados enviados pelo cliente. Uma chamada é feita para o método SendAsync
em Clients.All. As mensagens recebidas são enviadas a todos os clientes conectados ao hub.
Testar o aplicativo
Confirme que o aplicativo funciona com as seguintes etapas.
Visual Studio
Visual Studio Code
1. Execute o Webpack no modo de versão. Usando a janela Console do Gerenciador de Pacotes, execute o
comando a seguir na raiz do projeto. Se você não estiver na raiz do projeto, insira cd SignalRWebPack antes
de inserir o comando.
Este comando suspende o fornecimento dos ativos do lado do cliente ao executar o aplicativo. Os ativos são
colocados na pasta wwwroot.
O Webpack concluiu as seguintes tarefas:
Limpou os conteúdos do diretório wwwroot.
Converteu o TypeScript para JavaScript, um processo conhecido como transpilação.
Reduziu o tamanho do arquivo JavaScript gerado, um processo conhecido como minificação.
Copiou os arquivos JavaScript, CSS e HTML processados do src para o diretório wwwroot.
Injetou os seguintes elementos no arquivo wwwroot/index.html:
Uma marca <link> , que referencia o arquivo wwwroot/main.<hash>.css. Essa marca é colocada
imediatamente antes do fim da marca </head> .
Uma marca <script> , que referencia o arquivo minificado wwwroot/main.<hash>.js. Essa marca
é colocada imediatamente antes do fim da marca </body> .
2. Selecione Debug > Iniciar sem depuração para iniciar o aplicativo em um navegador sem anexar o
depurador. O arquivo wwwroot/index.html é fornecido em http://localhost:<port_number> .
3. Abra outra instância do navegador (qualquer navegador). Cole a URL na barra de endereços.
4. Escolha qualquer navegador, digite algo na caixa de texto Mensagem e clique no botão Enviar. O nome de
usuário exclusivo e a mensagem são exibidas em ambas as páginas instantaneamente.
Recursos adicionais
ASP.NET Core SignalR JavaScript cliente
Usando os hubs de SignalR do ASP.NET Core
Usar os hubs no SignalR do ASP.NET Core
25/01/2019 • 13 minutes to read • Edit Online
services.AddSignalR();
Ao adicionar a funcionalidade do SignalR para um aplicativo ASP.NET Core, configurar as rotas do 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#. O SignalR lida com a serialização e desserialização de objetos complexos e matrizes
em seus valores de retorno e parâmetros.
NOTE
Os hubs são transitórios:
Não armazene o estado em uma propriedade na classe hub. Cada chamada de método de hub é executada em uma
nova instância de hub.
Use await ao chamar métodos assíncronos que dependem do hub de permanecer ativo. Por exemplo, um método,
como Clients.All.SendAsync(...) pode falhar se ele for chamado sem await e o método de hub seja concluída
antes de SendAsync for concluída.
O objeto de contexto
O Hub classe tem um Context propriedade que contém as propriedades a seguir com informações sobre a
conexão:
PROPRIEDADE DESCRIÇÃO
Items Obtém uma coleção de chave/valor que pode ser usada para
compartilhar dados dentro do escopo dessa conexão. Dados
podem ser armazenados nessa coleção e ela será mantida
para a conexão entre as invocações de método de hub
diferentes.
MÉTODO DESCRIÇÃO
O objeto de clientes
O Hub classe tem um Clients propriedade que contém as seguintes propriedades 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 parâmetros do método de cliente para chamar.
Essa interface pode ser usada para refatorar anterior ChatHub exemplo.
Usando Hub<IChatClient> habilita a verificação de tempo de compilação dos métodos do cliente. Isso evita
problemas causados pelo uso de cadeias de caracteres mágicas desde Hub<T> só pode fornecer acesso aos
métodos definidos na interface.
Usando fortemente tipado Hub<T> desabilita a capacidade de usar SendAsync . Todos os métodos definidos na
interface ainda podem ser definidos como assíncronos. Na verdade, cada um desses métodos deve retornar um
Task . Uma vez que ele é uma interface, não use o async palavra-chave. Por exemplo:
[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
return Clients.User(user).SendAsync("ReceiveMessage", message);
}
Substituir o OnDisconnectedAsync método virtual para executar ações quando um cliente se desconecta. Se o
cliente se desconecta intencionalmente (chamando connection.stop() , por exemplo), o exception parâmetro
será null . No entanto, se o cliente for desconectado devido a um erro (como uma falha de rede), o exception
parâmetro conterá uma exceção que descreve a falha.
Tratar erros
As 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 tem chamado e passado como um JavaScript Error
objeto.
Se o seu Hub de lançar uma exceção, as conexões não estão fechadas. Por padrão, o SignalR retorna uma
mensagem de erro genérica para o cliente. Por exemplo:
Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.
Exceções inesperadas geralmente contêm informações confidenciais, como o nome de um servidor de banco de
dados em uma exceção acionada quando a conexão de banco de dados falha. O SignalR não expõe estas
mensagens de erro detalhadas por padrão como medida de segurança. Consulte a artigo de considerações de
segurança para obter mais informações sobre por que os detalhes da exceção são suprimidos.
Se você tiver um excepcional de condição você fazer deseja propagar para o cliente, você pode usar o
HubException classe. Se você lançar uma HubException de seu método de hub do SignalR será enviar a
mensagem inteira para o cliente, sem modificações.
NOTE
O SignalR envia apenas o Message propriedade da exceção para o cliente. O rastreamento de pilha e outras propriedades
na exceção não estão disponíveis para o cliente.
Recursos relacionados
Introdução ao SignalR do ASP.NET Core
Cliente JavaScript
Publicar no Azure
Enviar mensagens de fora de um hub
25/01/2019 • 3 minutes to read • Edit Online
NOTE
Isso é diferente do ASP.NET 4.x SignalR que usado GlobalHost para fornecer acesso ao IHubContext . O ASP.NET Core tem
uma estrutura de injeção de dependência que remove a necessidade de neste singleton global.
Agora, com acesso a uma instância de IHubContext , você pode chamar métodos de hub, como se estivessem no
próprio hub.
NOTE
Quando os métodos de hub são chamados de fora do Hub de classe, não há nenhum chamador associado com a
invocação. Portanto, não há nenhum acesso para o ConnectionId , Caller , e Others propriedades.
Recursos relacionados
Introdução
Hubs
Publicar no Azure
Gerenciar usuários e grupos no SignalR
02/02/2019 • 4 minutes to read • Edit Online
Usuários no SignalR
O SignalR permite enviar mensagens para todas as conexões associadas a um usuário específico. Por padrão, o
SignalR usa o ClaimTypes.NameIdentifier do ClaimsPrincipal associado com a conexão como o identificador de
usuário. Um único usuário pode ter várias conexões a um aplicativo do SignalR. Por exemplo, um usuário pode
ser conectado em sua área de trabalho, bem como seu telefone. Cada dispositivo tem uma conexão SignalR
separado, mas eles são todos associados ao mesmo usuário. Se uma mensagem é enviada para o usuário, todas
as conexões associadas ao usuário recebem a mensagem. O identificador de usuário para uma conexão pode ser
acessado pelo Context.UserIdentifier propriedade em seu hub.
Enviar uma mensagem para um usuário específico, passando o identificador de usuário para o User funcionar no
seu método de hub, conforme mostrado no exemplo a seguir:
NOTE
O identificador de usuário diferencia maiusculas de minúsculas.
Grupos no SignalR
Um grupo é uma coleção de conexões associado com um nome. As mensagens podem ser enviadas para todas as
conexões em um grupo. Grupos são a maneira recomendada para enviar para uma conexão ou várias conexões,
porque os grupos são gerenciados pelo aplicativo. Uma conexão pode ser um membro de vários grupos. Isso
torna grupos ideal para algo como um aplicativo de bate-papo, onde cada sala pode ser representada como um
grupo. Conexões podem ser adicionadas ou removidas de grupos por meio de AddToGroupAsync e
RemoveFromGroupAsync métodos.
public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
Associação de grupo não é preservada quando uma conexão se reconecta. A conexão precisa ingressar
novamente o grupo quando é restabelecida. Não é possível contar os membros de um grupo, uma vez que essas
informações não estarão disponíveis se o aplicativo é dimensionado para vários servidores.
Para proteger o acesso a recursos durante o uso de grupos, use autenticação e autorização funcionalidade no
ASP.NET Core. Se você adicionar apenas os usuários a um grupo quando as credenciais são válidas para esse
grupo, as mensagens enviadas a esse grupo só irá para os usuários autorizados. No entanto, os grupos não são
um recurso de segurança. Declarações de autenticação têm recursos que grupos não fizer isso, como a expiração e
a revogação. Se a permissão do usuário para acessar o grupo for revogada, você precisa detectar que e removê-
las manualmente.
NOTE
Nomes de grupo diferenciam maiusculas de minúsculas.
Recursos relacionados
Introdução
Hubs
Publicar no Azure
Publicar um ASP.NET Core aplicativo SignalR em um
aplicativo Web do Azure
25/01/2019 • 3 minutes to read • Edit Online
Aplicativo Web do Azure é um a computação em nuvem do Microsoft plataforma de serviço para hospedar
aplicativos web, incluindo o ASP.NET Core.
NOTE
Este artigo refere-se a publicação de um aplicativo de SignalR do ASP.NET Core no Visual Studio. Visite serviço SignalR para
o Azure para obter mais informações sobre como usar o SignalR no Azure.
Publique o aplicativo
Visual Studio fornece ferramentas internas para publicação de um aplicativo da 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 ASP.NET Core no Azure com ferramentas de linha de comando.
Clique com botão direito no projeto no Gerenciador de soluções e selecione publicar. Confirme criar novo
check-in a escolher um destino de publicação caixa de diálogo e selecione publicar.
Insira as seguintes informações na criar serviço de aplicativo caixa de diálogo e selecione criar.
ITEM DESCRIÇÃO
Se ocorrer um erro de HTTP 502.2, consulte versão de visualização de implantar o ASP.NET Core no serviço de
aplicativo do Azure resolvê-lo.
WebSockets e outros transportes são limitados com base no plano do serviço de aplicativo.
Recursos relacionados
Publicar um aplicativo ASP.NET Core no Azure com ferramentas de linha de comando
Publicar um aplicativo ASP.NET Core no Azure com o Visual Studio
Hospedar e implantar aplicativos de visualização do ASP.NET Core no Azure
Considerações de design de API do SignalR
13/11/2018 • 4 minutes to read • Edit Online
connection.invoke("GetTotalLength", "value1");
Se você adicionar posteriormente um segundo parâmetro para o método de servidor, os clientes mais antigos não
fornecerá esse valor de parâmetro. Por exemplo:
Quando o cliente antigo tenta invocar esse método, ele receberá um erro como este:
O cliente antigo enviados apenas um parâmetro, mas a API mais recente do servidor necessários dois parâmetros.
O uso de objetos personalizados como parâmetros oferece mais flexibilidade. Vamos recriar a API original para
usar um objeto personalizado:
public class TotalLengthRequest
{
public string Param1 { get; set; }
}
Quando o cliente antigo envia um único parâmetro, o extra Param2 propriedade será deixada null . Você pode
detectar uma mensagem enviada por um cliente mais antigo, verificando o Param2 para null e aplicar um valor
padrão. Um novo cliente pode enviar os dois parâmetros.
A mesma técnica funciona para métodos definidos no cliente. Você pode enviar um objeto personalizado do lado
do servidor:
Se você decidir posteriormente adicionar o remetente da mensagem à carga, adicione uma propriedade no objeto:
public async Task Broadcast(string message)
{
await Clients.All.SendAsync("ReceiveMessage", new
{
Sender = Context.User.Identity.Name,
Message = message
});
}
Os clientes mais antigos não esperando o Sender de valor, portanto eles vai ignorá-lo. Um novo cliente pode
aceitá-lo com a atualização para a nova propriedade de leitura:
Nesse caso, o novo cliente também é tolerante a falhas de um servidor antigo que não fornece o Sender valor.
Uma vez que o servidor antigo não fornecerá a Sender valor, o cliente verifica para ver se ele existe antes de
acessá-lo.
Cliente de .NET do SignalR do ASP.NET Core
24/01/2019 • 4 minutes to read • Edit Online
A biblioteca de cliente .NET de SignalR do ASP.NET Core permite que você se comunicar com os hubs de
SignalR em aplicativos .NET.
NOTE
Xamarin tem requisitos especiais para a versão do Visual Studio. Para obter mais informações, consulte cliente SignalR 2.1.1
no Xamarin.
Install-Package Microsoft.AspNetCore.SignalR.Client
Conectar a um hub
Para estabelecer uma conexão, cria uma HubConnectionBuilder e chamar Build . A URL do hub, protocolo, o tipo
de transporte, nível de log, cabeçalhos e outras opções podem ser configuradas durante a criação de uma
conexão. Configurar as opções necessárias, inserindo qualquer um dos HubConnectionBuilder métodos em Build
. Iniciar a conexão com StartAsync .
using System;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.AspNetCore.SignalR.Client;
namespace SignalRChatClient
{
public partial class MainWindow : Window
{
HubConnection connection;
public MainWindow()
{
InitializeComponent();
try
{
await connection.StartAsync();
messagesList.Items.Add("Connection started");
connectButton.IsEnabled = false;
sendButton.IsEnabled = true;
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
}
O principal motivo para o suporte assíncrono é portanto, você pode reiniciar a conexão. Iniciar uma conexão é
uma ação assíncrona.
Em um Closed manipulador que reinicia a conexão, considere aguardar algum atraso aleatório evitar
sobrecarregar o servidor, conforme mostrado no exemplo a seguir:
await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);
O código anterior no connection.On é executado quando o código do lado do servidor chama-o usando o
SendAsync método.
try
{
await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
Recursos adicionais
Hubs
Cliente JavaScript
Publicar no Azure
Cliente de Java do SignalR Core ASP.NET
05/12/2018 • 5 minutes to read • Edit Online
implementation 'com.microsoft.signalr:signalr:1.0.0'
Se usando o Maven, adicione as seguintes linhas dentro de <dependencies> elemento da sua POM. XML arquivo:
<dependency>
<groupId>com.microsoft.signalr</groupId>
<artifactId>signalr</artifactId>
<version>1.0.0</version>
</dependency>
Conectar a um hub
Para estabelecer uma HubConnection , o HubConnectionBuilder deve ser usado. O nível de log e a URL do hub pode
ser configurado durante a criação de uma conexão. Configurar as opções necessárias chamando o
HubConnectionBuilder métodos antes build . Iniciar a conexão com start .
hubConnection.send("Send", input);
implementation 'org.slf4j:slf4j-jdk14:1.7.25'
Se você não configurar o registro em log em suas dependências, SLF4J carrega um agente de operação não
padrão com a seguinte mensagem de aviso:
Limitações conhecidas
Há suporte para apenas o protocolo JSON.
Há suporte para apenas o transporte de WebSockets.
Streaming ainda não tem suporte.
Recursos adicionais
Referência de API Java
Usando os hubs de SignalR do ASP.NET Core
ASP.NET Core SignalR JavaScript cliente
Publicar um ASP.NET Core SignalR aplicativo ao aplicativo Web do Azure
ASP.NET Core SignalR JavaScript cliente
24/01/2019 • 7 minutes to read • Edit Online
npm init -y
npm install @aspnet/signalr
NPM instala o conteúdo do pacote na node_modules\@aspnet\signalr\dist\browser pasta. Criar uma nova pasta
chamada signalr sob o wwwroot\lib pasta. Cópia de signalr.js do 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
Clientes JavaScript chamem métodos públicos em hubs por meio de invocar método da HubConnection. O
invoke método aceita dois argumentos:
O código anterior no connection.on é executado quando o código do lado do servidor chama-o usando o
SendAsync método.
O SignalR determina qual método de cliente para chamar, correspondendo o nome do método e argumentos
definidos no SendAsync e connection.on .
NOTE
Como uma prática recomendada, chame o inicie método na HubConnection depois on . Isso garante que os
manipuladores são registrados antes de todas as mensagens são recebidas.
connection.start().catch(function (err) {
return console.error(err.toString());
});
Configure o rastreamento de log do lado do cliente, passando um agente de log e o tipo de evento para
registrar em log quando a conexão é feita. As mensagens são registradas com o nível de log especificado e
superior. Níveis de log disponíveis são da seguinte maneira:
signalR.LogLevel.Error – Mensagens de erro. Logs Error somente mensagens.
signalR.LogLevel.Warning – Mensagens de aviso sobre erros em potencial. Os logs Warning , e Error
mensagens.
signalR.LogLevel.Information – Mensagens de status sem erros. Os logs Information , Warning , e Error
mensagens.
signalR.LogLevel.Trace – Mensagens de rastreamento. Registra tudo, incluindo dados transportados entre
cliente e o hub.
Use o configureLogging método HubConnectionBuilder para configurar o nível de log. As mensagens são
registradas para o console do navegador.
Os clientes de reconexão
O cliente JavaScript para o SignalR não reconectar-se automaticamente. Você deve escrever código que será
reconectada seu cliente manualmente. O código a seguir demonstra uma abordagem típica de reconexão:
1. Uma função (nesse caso, o start função) é criado para iniciar a conexão.
2. Chame o start função em que a conexão onclose manipulador de eventos.
connection.onclose(async () => {
await start();
});
Uma implementação real seria usar uma retirada exponencial ou repetir um número especificado de vezes antes
de desistir.
Recursos adicionais
Referência de API JavaScript
Tutorial do JavaScript
Tutorial de WebPack e TypeScript
Hubs
Cliente .NET
Publicar no Azure
Solicitações entre origens (CORS )
Hospedagem do ASP.NET SignalR Core e
dimensionamento
25/01/2019 • 8 minutes to read • Edit Online
An attempt was made to access a socket in a way forbidden by its access permissions...
Para evitar o uso de recursos do SignalR causando erros em outros aplicativos da web, execute o SignalR em
servidores diferentes do que seus outros aplicativos da web.
Para evitar o uso de recursos do SignalR causando erros em um aplicativo do SignalR, escala horizontalmente
para limitar o número de conexões que um servidor deve manipular.
Expansão do
Um aplicativo que usa o SignalR precisa manter o controle de todas as suas conexões, que cria problemas para um
farm de servidores. Adicionar um servidor, e ele obtém novas conexões de outros servidores não conhecer. Por
exemplo, o SignalR em cada servidor no diagrama a seguir desconhece as conexões nos outros servidores.
Quando quiser SignalR em um dos servidores enviar uma mensagem a todos os clientes, a mensagem é apenas
vai para os clientes conectados a esse servidor.
As opções para resolver esse problema são as serviço do Azure SignalR e Redis backplane.
O resultado é que o serviço gerencia todas as conexões de cliente, enquanto cada servidor precisa de apenas um
pequeno número constante de conexões para o serviço, conforme mostrado no diagrama a seguir:
Essa abordagem de expansão tem várias vantagens em relação a alternativa de backplane do Redis:
Sessões adesivas, também conhecidas como afinidade do cliente, não é necessário, pois os clientes
imediatamente são redirecionados para o serviço do Azure SignalR quando eles se conectam.
Um aplicativo pode escalar horizontalmente de SignalR com base no número de mensagens enviadas,
enquanto o serviço do Azure SignalR é dimensionado automaticamente para lidar com qualquer número de
conexões. Por exemplo, pode haver milhares de clientes, mas se apenas algumas mensagens por segundo são
enviadas, o aplicativo SignalR não será necessário escalar horizontalmente em vários servidores apenas para
lidar com as conexões em si.
Um aplicativo de SignalR não usar significativamente mais recursos de conexão que um aplicativo web sem
SignalR.
Por esses motivos, recomendamos que o serviço do Azure SignalR para todos os aplicativos do SignalR do
ASP.NET Core hospedados no Azure, incluindo o serviço de aplicativo, as VMs e contêineres.
Para obter mais informações, consulte o documentação do serviço do Azure SignalR .
Backplane de redis
Redis é um repositório de chave-valor na memória que dá suporte a um sistema de mensagens com um modelo
de publicação/assinatura. O backplane SignalR Redis usa o recurso de publicação/assinatura para encaminhar
mensagens para outros servidores. Quando um cliente faz uma conexão, as informações de conexão são passadas
ao backplane. Quando um servidor deseja enviar uma mensagem a todos os clientes, ele envia ao backplane.
Backplane sabe clientes tudo conectados e quais servidores que eles estão. Ele envia a mensagem a todos os
clientes por meio de seus respectivos servidores. Esse processo é ilustrado no diagrama a seguir:
Próximas etapas
Para obter mais informações, consulte os seguintes recursos:
Documentação do SignalR Service do Azure
Configurar um backplane de Redis
Configurar um backplane de Redis para expansão do
SignalR do ASP.NET Core
25/01/2019 • 6 minutes to read • Edit Online
IMPORTANT
Para uso em produção, um backplane de Redis é recomendável somente quando ele é executado no mesmo data
center que o aplicativo do SignalR. Caso contrário, latência de rede degrada o desempenho. Se seu aplicativo SignalR
está em execução na nuvem do Azure, é recomendável o serviço do Azure SignalR em vez de um backplane de Redis.
Você pode usar o serviço de Cache Redis do Azure para desenvolvimento e ambientes de teste.
services.AddSignalR().AddRedis("<your_Redis_connection_string>");
services.AddSignalR()
.AddRedis(connectionString, options => {
options.Configuration.ChannelPrefix = "MyApp";
});
No código anterior, options.Configuration é inicializada com tudo o que foi especificado na cadeia de
conexão.
No aplicativo do SignalR, instale um dos seguintes pacotes NuGet:
-Depende do stackexchange. Redis 2.X.X. Este é o
Microsoft.AspNetCore.SignalR.StackExchangeRedis
pacote recomendado para o ASP.NET Core 2.2 e posterior.
Microsoft.AspNetCore.SignalR.Redis -Depende do 1.X.X stackexchange. Redis. Este pacote não enviará no
ASP.NET Core 3.0.
No Startup.ConfigureServices método, chame AddStackExchangeRedis depois AddSignalR :
services.AddSignalR().AddStackExchangeRedis("<your_Redis_connection_string>");
services.AddSignalR()
.AddStackExchangeRedis(connectionString, options => {
options.Configuration.ChannelPrefix = "MyApp";
});
No código anterior, options.Configuration é inicializada com tudo o que foi especificado na cadeia de
conexão.
Para obter informações sobre as opções do Redis, consulte o StackExchange Redis documentação.
Se você estiver usando um servidor de Redis para vários aplicativos do SignalR, use um prefixo de canal
diferente para cada aplicativo do SignalR.
Configurar um prefixo de canal isola a um aplicativo do SignalR de outras pessoas que usam os prefixos de
canal diferente. Se você não atribuir prefixos diferentes, uma mensagem enviada de um aplicativo para
todos os seus próprios clientes irão para todos os clientes de todos os aplicativos que usam o servidor Redis
como um backplane.
Configure seu servidor farm balanceamento de carga para sessões adesivas. Aqui estão alguns exemplos de
documentação sobre como fazer isso:
IIS
HAProxy
Nginx
pfSense
services.AddSignalR()
.AddRedis(o =>
{
o.ConnectionFactory = async writer =>
{
var config = new ConfigurationOptions
{
AbortOnConnectFail = false
};
config.EndPoints.Add(IPAddress.Loopback, 0);
config.SetDefaultPorts();
var connection = await ConnectionMultiplexer.ConnectAsync(config, writer);
connection.ConnectionFailed += (_, e) =>
{
Console.WriteLine("Connection to Redis failed.");
};
if (!connection.IsConnected)
{
Console.WriteLine("Did not connect to Redis.");
}
return connection;
};
});
services.AddSignalR()
.AddMessagePackProtocol()
.AddStackExchangeRedis(o =>
{
o.ConnectionFactory = async writer =>
{
var config = new ConfigurationOptions
{
AbortOnConnectFail = false
};
config.EndPoints.Add(IPAddress.Loopback, 0);
config.SetDefaultPorts();
var connection = await ConnectionMultiplexer.ConnectAsync(config, writer);
connection.ConnectionFailed += (_, e) =>
{
Console.WriteLine("Connection to Redis failed.");
};
if (!connection.IsConnected)
{
Console.WriteLine("Did not connect to Redis.");
}
return connection;
};
});
Clustering
O clustering é um método para alcançar alta disponibilidade por meio de vários servidores do Redis. Clustering
não é oficialmente suportado, mas pode funcionar.
Próximas etapas
Para obter mais informações, consulte os seguintes recursos:
Hospedagem de produção do ASP.NET SignalR Core e dimensionamento
Documentação do redis
Documentação do StackExchange Redis
Documentação do Cache Redis do Azure
Host SignalR do ASP.NET Core em serviços em
segundo plano
05/02/2019 • 5 minutes to read • Edit Online
app.UseSignalR((routes) =>
{
routes.MapHub<ClockHub>("/hubs/clock");
});
}
}
No exemplo anterior, o ClockHub classe implementa o Hub<T> classe para criar um Hub com rigidez de tipos. O
ClockHub tiver sido configurado na Startup classe responder às solicitações no ponto de extremidade
/hubs/clock .
Para obter mais informações sobre os Hubs com rigidez de tipos, consulte usando os hubs de SignalR do ASP.NET
Core.
NOTE
Essa funcionalidade não está limitada para o Hub<T > classe. Qualquer classe que herda de Hub, como DynamicHub,
também funcionará.
services.AddHostedService<Worker>();
Uma vez que o SignalR também está conectado durante o Startup fase, em que cada Hub está anexado a um
ponto de extremidade individual no pipeline de solicitação HTTP do ASP.NET Core, cada Hub é representado por
um IHubContext<T> no servidor. Usando o ASP.NET Core injeção de dependência recursos, outras classes
instanciadas por meio da camada de hospedagem, como BackgroundService classes, classes de controlador MVC
ou modelos de página do Razor, podem obter referências para os Hubs do lado do servidor, aceitando as instâncias
de IHubContext<ClockHub, IClock> durante a construção.
Como o ExecuteAsync método é chamado de forma iterativa no serviço do plano de fundo, o servidor data e hora
atuais são enviadas para os clientes conectados usando o ClockHub .
_connection.On<DateTime>(Strings.Events.TimeSent,
dateTime => _ = ShowTime(dateTime));
}
return Task.CompletedTask;
}
break;
}
catch
{
await Task.Delay(1000);
}
}
}
Durante o IHostedService.StopAsync método, o HubConnection é descartado de forma assíncrona.
Recursos adicionais
Introdução
Hubs
Publicar no Azure
Hubs com rigidez de tipos
Configuração do ASP.NET SignalR Core
08/02/2019 • 19 minutes to read • Edit Online
services.AddSignalR()
.AddJsonProtocol(options => {
options.PayloadSerializerSettings.ContractResolver =
new DefaultContractResolver();
});
NOTE
Não é possível configurar a serialização JSON no cliente JavaScript neste momento.
NOTE
Não é possível configurar a serialização de MessagePack no cliente JavaScript neste momento.
Opções podem ser configuradas para todos os hubs, fornecendo um delegado de opções para o AddSignalR
chamar em Startup.ConfigureServices .
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR(hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
hubOptions.KeepAliveInterval = TimeSpan.FromMinutes(1);
});
}
Opções para um único hub substituem as opções de globais fornecidas no AddSignalR e pode ser configurado
usando AddHubOptions<T >:
services.AddSignalR().AddHubOptions<MyHub>(options =>
{
options.EnableDetailedErrors = true;
});
A tabela a seguir descreve opções para configurar opções avançadas de HTTP do SignalR do ASP.NET Core:
O transporte de sondagem longa tem opções adicionais que podem ser configuradas usando o LongPolling
propriedade:
O transporte de WebSocket tem opções adicionais que podem ser configuradas usando o WebSockets
propriedade:
NOTE
Para registrar os provedores de log, você deve instalar os pacotes necessários. Consulte a provedores de log internos seção
do docs para obter uma lista completa.
Por exemplo, para habilitar o log de Console, instale o Microsoft.Extensions.Logging.Console pacote do NuGet.
Chamar o AddConsole método de extensão:
No cliente JavaScript, um semelhante configureLogging existe um método. Forneça um LogLevel valor que indica
o nível mínimo de mensagens de log para produzir. Os logs são gravados na janela de console do navegador.
NOTE
Para desabilitar o registro em log totalmente, especifique signalR.LogLevel.None no configureLogging método.
Níveis de log disponíveis para o cliente JavaScript estão listados abaixo. Definindo o nível de log para um desses
valores habilita o log de mensagens no ou superior desse nível.
NÍVEL DESCRIÇÃO
No cliente JavaScript, transportes são configurados, definindo o transport campo no objeto de opções fornecido
ao withUrl :
No cliente JavaScript, o token de acesso é configurado definindo a accessTokenFactory campo no objeto de opções
no withUrl :
No cliente do .NET, os valores de tempo limite são especificados como TimeSpan valores. No cliente JavaScript, os
valores de tempo limite são especificados como um número que indica a duração em milissegundos.
Configurar opções adicionais
Opções adicionais podem ser configuradas na WithUrl ( withUrl em JavaScript) método em
HubConnectionBuilder :
Opções marcadas com um asterisco (*) não são configuráveis no cliente JavaScript, devido a limitações no
navegador APIs.
No cliente do .NET, essas opções podem ser modificadas pelo delegado opções fornecido para WithUrl :
Recursos adicionais
Introdução ao SignalR para ASP.NET Core
Usando os hubs de SignalR do ASP.NET Core
ASP.NET Core SignalR JavaScript cliente
Cliente de .NET do SignalR do ASP.NET Core
Usar o protocolo de MessagePack Hub no SignalR do ASP.NET Core
Plataformas com suporte do SignalR do ASP.NET Core
Autenticação e autorização no SignalR do ASP.NET
Core
02/02/2019 • 11 minutes to read • Edit Online
No cliente do .NET, há um semelhante AccessTokenProvider propriedade que pode ser usada para configurar o
token:
APIs da web padrão, os tokens de portador são enviados em um cabeçalho HTTP. No entanto, o SignalR é não é
possível definir esses cabeçalhos em navegadores quando usando alguns transportes. Ao usar WebSockets e
eventos do Server-Sent, o token é transmitido como um parâmetro de cadeia de caracteres de consulta. Para
suportar isso no servidor, a configuração adicional é necessária:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
// Identity made Cookie authentication the default.
// However, we want JWT Bearer Auth to be the default.
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// Configure JWT Bearer Auth to expect our security key
options.TokenValidationParameters =
new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, param) =>
{
return expires > DateTime.UtcNow;
},
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
Em vez de ClaimTypes.Name , você pode usar qualquer valor entre o User (como o identificador do SID do
Windows, etc.).
NOTE
O valor escolhido deve ser exclusivo entre todos os usuários em seu sistema. Caso contrário, uma mensagem para um
usuário poderia acabar indo para um usuário diferente.
services.AddSignalR();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}
Autenticação do Windows só tem suporte pelo cliente do navegador ao usar o Microsoft Internet Explorer ou
Microsoft Edge.
Uso de declarações para personalizar o tratamento de identidade
Um aplicativo que autentica usuários pode derivar as IDs de usuário do SignalR de declarações de usuário. Para
especificar como o SignalR cria IDs de usuário, implementar IUserIdProvider e registrar a implementação.
O código de exemplo demonstra como você usaria declarações para selecionar o endereço de email do usuário
como a propriedade de identificação.
NOTE
O valor escolhido deve ser exclusivo entre todos os usuários em seu sistema. Caso contrário, uma mensagem para um
usuário poderia acabar indo para um usuário diferente.
O registro de conta adiciona uma declaração com o tipo ClaimsTypes.Email no banco de dados de identidade do
ASP.NET.
services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
Autorizar usuários para acesso hubs e métodos de hub
Por padrão, todos os métodos em um hub podem ser chamados por um usuário não autenticado. Para exigir
autenticação, se aplicam a autorizar de atributo para o hub:
[Authorize]
public class ChatHub: Hub
{
}
Você pode usar os argumentos de construtor e propriedades do [Authorize] atributo para restringir o acesso
somente para usuários específicos de correspondência políticas de autorização. Por exemplo, se você tiver uma
política de autorização personalizada chamada MyAuthorizationPolicy pode garantir que somente os usuários
dessa política de correspondência podem acessar o hub usando o seguinte código:
[Authorize("MyAuthorizationPolicy")]
public class ChatHub: Hub
{
}
Métodos de hub individuais podem ter o [Authorize] atributo aplicado também. Se o usuário atual não
corresponder a política aplicada ao método, um erro será retornado ao chamador:
[Authorize]
public class ChatHub: Hub
{
public async Task Send(string message)
{
// ... send a message to all users ...
}
[Authorize("Administrators")]
public void BanUser(string userName)
{
// ... ban a user from the chat room (something only Administrators can do) ...
}
}
Recursos adicionais
Autenticação de Token de portador no ASP.NET Core
Considerações de segurança no SignalR do ASP.NET
Core
24/01/2019 • 9 minutes to read • Edit Online
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
NOTE
O cabeçalho Origin é controlado pelo cliente e, como o cabeçalho Referer , pode ser falsificado. Esses cabeçalhos devem
não ser usado como um mecanismo de autenticação.
Se você tiver dúvidas sobre o log de dados com os logs do servidor, você pode desabilitar esse log inteiramente,
configurando o Microsoft.AspNetCore.Hosting agente para o Warning nível ou superior (essas mensagens são
gravadas em Info nível). Consulte a documentação sobre filtragem de Log para obter mais informações. Se você
ainda quiser registrar determinadas informações de solicitação, você poderá escrever um middleware para
registrar os dados necessários e filtrar o access_token valor de cadeia de caracteres de consulta (se presente).
Exceções
Mensagens de exceção são geralmente consideradas dados confidenciais que não devem ser revelados para um
cliente. Por padrão, o SignalR não envia os detalhes de uma exceção gerada por um método de hub para o cliente.
Em vez disso, o cliente recebe uma mensagem genérica indicando que ocorreu um erro. Entrega de mensagem de
exceção para o cliente pode ser substituída (por exemplo, no desenvolvimento ou teste) com EnableDetailedErrors
. Mensagens de exceção não devem ser expostas ao cliente em aplicativos de produção.
Gerenciamento de buffer
O SignalR usa buffers por conexão para gerenciar mensagens de entrada e saídas. Por padrão, o SignalR limita
esses buffers para 32 KB. A mensagem maior que um cliente ou servidor pode enviar é 32 KB. O máximo de
memória consumido por uma conexão de mensagens é 32 KB. Se as mensagens são sempre menores que 32 KB,
você pode reduzir o limite, que:
Impede que um cliente que está sendo capaz de enviar uma mensagem maior.
O servidor nunca será necessário alocar buffers grandes para aceitar mensagens.
Se suas mensagens forem maiores que 32 KB, você pode aumentar o limite. Aumentar esse limite significa:
O cliente pode fazer com que o servidor para alocar buffers de memória grandes.
Alocação de servidor de buffers grandes pode reduzir o número de conexões simultâneas.
Há limites para mensagens de entrada e saídas, ambos podem ser configuradas sobre o
HttpConnectionDispatcherOptions objeto configurado no MapHub :
O que é MessagePack?
MessagePack é um formato de serialização binária que é rápido e compacto. Ela é útil quando o desempenho e a
largura de banda são uma preocupação porque cria mensagens menores em comparação comparadas JSON.
Porque ele é um formato binário, as mensagens são ilegíveis ao examinar os logs e rastreamentos de rede, a
menos que os bytes são passados por meio de um analisador MessagePack. O SignalR tem suporte interno para
o formato MessagePack e fornece APIs para o cliente e servidor usar.
NOTE
JSON é habilitado por padrão. Adicionar MessagePack habilita o suporte para JSON e MessagePack clientes.
services.AddSignalR()
.AddMessagePackProtocol();
Para personalizar como MessagePack formatará a seus dados, AddMessagePackProtocol aceita um delegado para
configurar as opções. No delegado, o FormatterResolvers propriedade pode ser usada para configurar as opções
de serialização MessagePack. Para obter mais informações sobre como funcionam os resolvedores, visite a
biblioteca, MessagePack em MessagePack-CSharp. Atributos podem ser usados nos objetos que você deseja
serializar para definir como eles devem ser tratados.
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MessagePack.Resolvers.StandardResolver.Instance
};
});
Cliente .NET
Para habilitar MessagePack no cliente .NET, instale o Microsoft.AspNetCore.SignalR.Protocols.MessagePack pacote
e chame AddMessagePackProtocol em HubConnectionBuilder .
NOTE
Isso AddMessagePackProtocol chamada aceita um delegado para configurar opções de como o servidor.
Cliente JavaScript
MessagePack suporte para o cliente JavaScript é fornecido pelo @aspnet/signalr-protocol-msgpack pacote npm.
Depois de instalar o pacote npm, o módulo pode ser usado diretamente por meio de um carregador de módulo
de JavaScript ou importado para o navegador fazendo referência a node_modules\@aspnet\signalr-protocol-
msgpack\dist\browser\signalr-protocol-msgpack.js arquivo. Em um navegador, o msgpack5 biblioteca também
deve ser referenciada. Use um <script> marca para criar uma referência. A biblioteca pode ser encontrada em
node_modules\msgpack5\dist\msgpack5.js.
NOTE
Ao usar o <script> elemento, a ordem é importante. Se signalr-protocol-msgpack.js é referenciada antes msgpack5.js,
ocorre um erro quando tentar se conectar com MessagePack. SignalR.js também é necessária antes signalr-protocol-
msgpack.js.
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>
Recursos relacionados
Introdução
Cliente .NET
Cliente JavaScript
Usar o streaming em SignalR do ASP.NET Core
25/01/2019 • 7 minutes to read • Edit Online
Configurar o hub
Um método de hub automaticamente se torna um método de hub streaming quando ele retorna um
ChannelReader<T> ou um Task<ChannelReader<T>> . Abaixo está um exemplo que mostra os conceitos básicos do
fluxo de dados para o cliente. Sempre que um objeto é gravado o ChannelReader esse objeto é enviado
imediatamente para o cliente. No final, o ChannelReader estiver concluído para dizer ao cliente o fluxo está
fechado.
NOTE
Gravar o ChannelReader em um thread em segundo plano e retorne o ChannelReader assim que possível. Outras
chamadas de hub serão bloqueadas até que um ChannelReader é retornado.
Encapsular sua lógica em uma try ... catch e conclua o Channel em catch e fora de catch para garantir que o hub
de invocação de método é concluída corretamente.
public class StreamHub : Hub
{
public ChannelReader<int> Counter(int count, int delay)
{
var channel = Channel.CreateUnbounded<int>();
return channel.Reader;
}
writer.TryComplete();
}
}
public class StreamHub : Hub
{
public ChannelReader<int> Counter(
int count,
int delay,
CancellationToken cancellationToken)
{
var channel = Channel.CreateUnbounded<int>();
return channel.Reader;
}
writer.TryComplete();
}
}
No ASP.NET Core 2.2 ou posterior, os métodos de Hub de streaming podem aceitar um CancellationToken
parâmetro que será acionado quando o cliente cancela a assinatura do fluxo. Use esse token para interromper a
operação do servidor e liberar quaisquer recursos se o cliente se desconecta antes do final do fluxo.
Cliente .NET
O StreamAsChannelAsync método no HubConnection é usado para invocar um método de transmissão. Passe o
nome do método de hub e argumentos definidos no método de hub para StreamAsChannelAsync . O parâmetro
genérico em StreamAsChannelAsync<T> Especifica o tipo de objetos retornados pelo método de transmissão. Um
ChannelReader<T> é retornada da invocação de fluxo e representa o fluxo no cliente. Para ler dados, um padrão
comum é executar um loop sobre WaitToReadAsync e chamar TryRead quando os dados estiverem disponíveis. O
loop terminará quando o fluxo foi fechado pelo servidor ou o token de cancelamento passado para
StreamAsChannelAsync é cancelada.
// Call "Cancel" on this CancellationTokenSource to send a cancellation message to
// the server, which will trigger the corresponding token in the Hub method.
var cancellationTokenSource = new CancellationTokenSource();
var channel = await hubConnection.StreamAsChannelAsync<int>(
"Counter", 10, 500, cancellationTokenSource.Token);
Console.WriteLine("Streaming completed");
Console.WriteLine("Streaming completed");
Cliente JavaScript
Os clientes JavaScript chamar métodos de streaming em hubs usando connection.stream .O stream método
aceita dois argumentos:
O nome do método de hub. No exemplo a seguir, o nome do método de hub é Counter .
Argumentos definidos no método de hub. No exemplo a seguir, os argumentos são: uma contagem do número
de itens de fluxo para receber e o atraso entre itens de fluxo.
connection.stream Retorna um IStreamResult que contém um subscribe método. Passar uma
IStreamSubscriber para subscribe e defina as next , error , e complete retornos de chamada para receber as
notificações a stream invocação.
connection.stream("Counter", 10, 500)
.subscribe({
next: (item) => {
var li = document.createElement("li");
li.textContent = item;
document.getElementById("messagesList").appendChild(li);
},
complete: () => {
var li = document.createElement("li");
li.textContent = "Stream completed";
document.getElementById("messagesList").appendChild(li);
},
error: (err) => {
var li = document.createElement("li");
li.textContent = err;
document.getElementById("messagesList").appendChild(li);
},
});
Para terminar o fluxo do cliente, chame o dispose método em de ISubscription que é retornado do subscribe
método.
Para terminar o fluxo do cliente, chame o dispose método em de ISubscription que é retornado do subscribe
método. Chamar esse método fará com que o CancellationToken parâmetro do método de Hub (se você tiver
fornecido um) a ser cancelada.
Recursos relacionados
Hubs
Cliente .NET
Cliente JavaScript
Publicar no Azure
Diferenças entre o SignalR do ASP.NET e o SignalR
do ASP.NET Core
25/01/2019 • 8 minutes to read • Edit Online
SignalR do ASP.NET Core não é compatível com clientes ou servidores para ASP.NET SignalR. Este artigo fornece
detalhes sobre os recursos que foram removidos ou alterados no SignalR do ASP.NET Core.
Plataformas de servidor com suporte .NET framework 4.5 ou posterior .NET Framework 4.6.1 ou posterior
.NET core 2.1 ou posterior
Diferenças de recursos
Reconexão automática
Reconexão automática não têm suporte no SignalR do ASP.NET Core. Se o cliente for desconectado, o usuário
explicitamente deve iniciar uma nova conexão, se eles desejam se reconectar. No ASP.NET SignalR, SignalR
tentará reconectar-se ao servidor se a conexão for interrompida.
Suporte de protocolo
SignalR do ASP.NET Core dá suporte a JSON, bem como um novo protocolo binário, com base em MessagePack.
Além disso, os protocolos personalizados podem ser criados.
Transportes
Não há suporte para o transporte de quadro para sempre SignalR do ASP.NET Core.
Diferenças no servidor
As bibliotecas do lado do servidor SignalR do ASP.NET Core são incluídas na metapacote do Microsoft que faz
parte do pacote a aplicativo Web ASP.NET Core modelo Razor e MVC projetos.
SignalR do ASP.NET Core é um middleware do ASP.NET Core, portanto, ele deve ser configurado por meio da
chamada AddSignalR em Startup.ConfigureServices .
services.AddSignalR()
Para configurar o roteamento, mapear as rotas para os hubs de dentro de UseSignalR chamada de método no
Startup.Configure método.
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/hub");
});
Sessões temporárias
O modelo de expansão do SignalR do ASP.NET permite que os clientes para se reconectar e enviar mensagens
para qualquer servidor no farm. No SignalR do ASP.NET Core, o cliente deve interagir com o mesmo servidor
durante a conexão. Para escala horizontal usando Redis, isso significa que as sessões temporárias são necessárias.
Para o uso de expansão serviço do Azure SignalR , sessões temporárias não são necessárias porque o serviço lida
com as conexões aos clientes.
Hub único por conexão
O SignalR do ASP.NET Core, o modelo de conexão foi simplificado. As conexões são feitas diretamente a um único
hub, em vez de uma única conexão está sendo usado para compartilhar o acesso a vários hubs.
Streaming
ASP.NET SignalR Core agora dá suporte à dados de streaming do hub para o cliente.
Estado
A capacidade de passar o estado arbitrário entre clientes e o hub (geralmente chamado de HubState) foi removida,
bem como suporte para mensagens de progresso. Não há nenhum equivalente de proxies de hub no momento.
Remoção de PersistentConnection
No SignalR do ASP.NET Core, o PersistentConnection classe foi removida.
GlobalHost
O ASP.NET Core tem dentro da estrutura de injeção de dependência (DI). Serviços podem usar a DI para acessar o
HubContext. O GlobalHost objeto que é usado no ASP.NET SignalR para obter um HubContext não existe no
SignalR do ASP.NET Core.
HubPipeline
SignalR do ASP.NET Core não tem suporte para HubPipeline módulos.
Diferenças no cliente
TypeScript
O cliente SignalR do ASP.NET Core é escrito em TypeScript. Você pode escrever em JavaScript ou TypeScript ao
usar o cliente JavaScript.
O cliente JavaScript é hospedado em npm
Nas versões anteriores, o cliente JavaScript foi obtido por meio de um pacote do NuGet no Visual Studio. Para as
versões de núcleo, o @aspnet/signalr pacote npm contém as bibliotecas de JavaScript. Este pacote não está
incluído na aplicativo Web ASP.NET Core modelo. Usar npm para obter e instalar o @aspnet/signalr pacote
npm.
npm init -y
npm install @aspnet/signalr
jQuery
A dependência no jQuery foi removida, no entanto, projetos ainda podem usar jQuery.
Suporte do Internet Explorer
SignalR do ASP.NET Core requer o Microsoft Internet Explorer 11 ou posterior (o SignalR do ASP.NET com
suporte Microsoft Internet Explorer 8 e posterior).
Sintaxe de método de cliente JavaScript
A sintaxe de JavaScript foi alterado da versão anterior do SignalR. Em vez de usar o $connection de objeto, criar
uma conexão usando o HubConnectionBuilder API.
Use o em método para especificar que o hub pode chamar métodos do cliente.
Depois de criar o método do cliente, inicie a conexão de hub. Cadeia de um catch método para fazer logon ou lidar
com erros.
Proxies de Hub
Os proxies de Hub não automaticamente são gerados. Em vez disso, o nome do método é passado para o invocar
API como uma cadeia de caracteres.
.NET e outros clientes
O Microsoft.AspNetCore.SignalR.Client pacote NuGet contém as bibliotecas de cliente .NET para o SignalR do
ASP.NET Core.
Use o HubConnectionBuilder para criar e compilar uma instância de uma conexão a um hub.
Diferenças de expansão
SignalR do ASP.NET oferece suporte a SQL Server e o Redis. SignalR do ASP.NET Core dá suporte ao serviço do
Azure SignalR e Redis.
ASP.NET
Expansão do SignalR com o barramento de serviço do Azure
Expansão do SignalR com Redis
Expansão do SignalR com o SQL Server
ASP.NET Core
Serviço Azure SignalR
Recursos adicionais
Hubs
Cliente JavaScript
Cliente .NET
Plataformas compatíveis
Suporte ao WebSockets no ASP.NET Core
30/01/2019 • 13 minutes to read • Edit Online
Pré-requisitos
ASP.NET Core 1.1 ou posterior
Qualquer sistema operacional compatível com o ASP.NET Core:
Windows 7/Windows Server 2008 ou posterior
Linux
macOS
Se o aplicativo é executado no Windows com o IIS:
Windows 8/Windows Server 2012 ou posterior
IIS 8/IIS 8 Express
WebSockets precisam ser habilitados (confira a seção Suporte para IIS/IIS Express).
Se o aplicativo é executado no HTTP.sys:
Windows 8/Windows Server 2012 ou posterior
Para saber quais são os navegadores compatíveis, confira https://caniuse.com/#feat=websockets.
app.UseWebSockets();
app.UseWebSockets();
app.UseWebSockets(webSocketOptions);
});
});
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 atualiza a 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 . O código recebe uma mensagem e envia de volta imediatamente a mesma mensagem. As
mensagens são enviadas e recebidas em um loop até que o cliente feche a conexão:
private async Task Echo(HttpContext context, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer),
CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType,
result.EndOfMessage, CancellationToken.None);
Ao aceitar a conexão WebSocket antes de iniciar o loop, o pipeline de middleware é encerrado. Ao fechar o
soquete, o pipeline é desenrolado. Ou seja, a solicitação deixa de avançar no pipeline quando o WebSocket é
aceito. Quando o loop é concluído e o soquete é fechado, a solicitação continua a avançar no pipeline.
Tratar desconexões de cliente
O servidor não é informado automaticamente quando o cliente se desconecta devido à perda de conectividade. O
servidor recebe uma mensagem de desconexão apenas quando o cliente a envia, e isso é possível somente
quando há conexão com a Internet. Se quiser tomar alguma medida quando isso ocorrer, defina um tempo limite,
caso não receba nada do cliente após uma determinada janela de tempo.
Se o cliente não envia mensagens com frequência, e você prefere não definir um tempo limite apenas porque a
conexão fica ociosa, peça ao cliente para usar um temporizador a fim de enviar uma mensagem de ping a cada X
segundos. No servidor, se uma mensagem não chegar dentro de 2*X segundos após a mensagem anterior,
encerre a conexão e informe que o cliente se desconectou. Aguarde o dobro do intervalo de tempo esperado a
fim de deixar um tempo extra para atrasos na rede, que possam atrasar a mensagem de ping.
Restrição de origem do WebSocket
As proteções fornecidas pelo CORS não se aplicam ao WebSockets. Navegadores não:
Executam solicitações de simulação de CORS.
Respeitam as restrições especificadas em cabeçalhos Access-Control ao fazer solicitações de WebSocket.
app.UseWebSockets(webSocketOptions);
NOTE
O cabeçalho Origin é controlado pelo cliente e, como o cabeçalho Referer , pode ser falsificado. Não use esses
cabeçalhos como um mecanismo de autenticação.
NOTE
WebSockets estão sempre habilitados ao usar o IIS Express.
NOTE
Estas etapas não são necessárias ao usar o IIS Express
1. Use o assistente Adicionar Funções e Recursos por meio do menu Gerenciar ou do link no Gerenciador
do Servidor.
2. Selecione Instalação baseada em função ou em recurso. Selecione Avançar.
3. Selecione o servidor apropriado (o servidor local é selecionado por padrão). Selecione Avançar.
4. Expanda Servidor Web (IIS ) na árvore Funções, expanda Servidor Web e, em seguida, expanda
Desenvolvimento de Aplicativos.
5. Selecione o Protocolo WebSocket. Selecione Avançar.
6. Se não forem necessários recursos adicionais, selecione Avançar.
7. Clique em Instalar.
8. Quando a instalação for concluída, selecione Fechar para sair do assistente.
Para habilitar o suporte para o protocolo WebSocket no Windows 8 ou posterior:
NOTE
Estas etapas não são necessárias ao usar o IIS Express
1. Navegue para Painel de Controle > Programas > Programas e Recursos > Ativar ou desativar recursos
do Windows (lado esquerdo da tela).
2. Abra os seguintes nós: Serviços de Informações da Internet > Serviços da World Wide Web >
Recursos de Desenvolvimento de Aplicativos.
3. Selecione o recurso Protocolo WebSocket. Selecione OK.
Desabilite o WebSocket ao usar o socket.io no Node.js
Se você estiver usando o suporte do WebSocket no socket.io no Node.js, desabilite o módulo do WebSocket do
IIS padrão usando o elemento webSocket em web.config ou em applicationHost.config. Se essa etapa não for
executada, o módulo do WebSocket do IIS tentará manipular a comunicação do WebSocket em vez do Node.js e
o aplicativo.
<system.webServer>
<webSocket enabled="false" />
</system.webServer>
Próximas etapas
O aplicativo de exemplo que acompanha este artigo é um aplicativo de eco. Ele tem uma página da Web que faz
conexões WebSocket e o servidor reenvia para o cliente todas as mensagens recebidas. Execute o aplicativo 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 exibe 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.
Testes de unidade de páginas do Razor no ASP.NET
Core
30/10/2018 • 17 minutes to read • Edit Online
Os testes podem ser executados usando os recursos de teste interno de um IDE, como Visual Studio. Se usando
Visual Studio Code ou a linha de comando, execute o seguinte comando em um prompt de comando na
tests/RazorPagesTestSample.Tests pasta:
dotnet test
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 escrever 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 seus Utilities método de classe TestingDbContextOptions
(tests/RazorPagesTestSample.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:
if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}
Há dois testes para esse método. Um teste verifica que o método exclui uma mensagem quando a mensagem
estiver 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.TestDbContextOptions()))
{
// 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 Arranjar, onde ocorre a preparação para a etapa atuar. 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 é definido para exclusão. Quando o DeleteMessageAsync 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();
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.TestDbContextOptions()))
{
// 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 de modelo de página são testados usando testes de sete na IndexPageTests classe
(tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.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.
Confirmar os métodos produzem correto IActionResult .
Verificando-se de que as atribuições de valor de propriedade são feitas corretamente.
Geralmente, esse grupo de testes de simular os métodos da DAL para produzir os dados esperados para a etapa
atuar em que um método de modelo de página é executado. Por exemplo, o GetMessagesAsync método da
AppDbContext é simulado para produzir uma saída. Quando este 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 uso 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 método é executado na etapa Act, ela chama o modelo de página GetMessagesAsync
método.
Etapa atuar de teste de unidade (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):
// Act
await pageModel.OnGetAsync();
O GetMessagesAsync método no DAL não retornar o resultado para esta chamada de método. A versão fictícia do
método retorna o resultado.
No Assert etapa, as mensagens reais ( actualMessages ) são atribuídos a partir de Messages propriedade do
modelo de página. Uma verificação de tipo também é executada quando as mensagens são atribuídas. As
mensagens esperadas e reais são comparadas por seus Text propriedades. O teste declara 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));
Outros testes neste grupo Criar página de objetos de modelo que incluam o DefaultHttpContext , o
ModelStateDictionary , um ActionContext para estabelecer a PageContext , um ViewDataDictionary e um
PageContext . Elas são úteis na realização de testes. Por exemplo, o aplicativo de mensagens estabelece uma
ModelState erro com AddModelError para verificar se válido 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);
}
Recursos adicionais
Teste de unidade em C# no .NET Core usando dotnet test e xUnit
Controladores de teste
Seu código de teste de unidade (Visual Studio)
Testes de integração
xUnit.net
Guia de Introdução xUnit.net (.NET Core/ASP.NET Core)
Moq
Guia de início rápido Moq
Lógica do controlador de teste no ASP.NET Core
10/11/2018 • 19 minutes to read • Edit Online
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 anterior:
Segue o Princípio de Dependências Explícitas.
Espera DI (injeção de dependência) para fornecer uma instância de IBrainstormSessionRepository .
Pode ser testado com um serviço IBrainstormSessionRepository fictício usando uma estrutura de objeto
fictício, como Moq. Um objeto fictício é um objeto fabricado com um conjunto predeterminado de
comportamentos de propriedade e de método usado para teste. Para saber mais, consulte Introdução aos
testes de integração.
O método HTTP GET Index não tem nenhum loop ou branch e chama apenas um método. O teste de unidade
para esta ação:
Imita o serviço IBrainstormSessionRepository usando o método GetTestSessions . GetTestSessions cria duas
sessões de debate fictícias com datas e nomes de sessão.
Executa o método Index .
Faz declarações sobre o resultado retornado pelo método:
Um ViewResult é retornado.
O ViewDataDictionary.Model é um StormSessionViewModel .
Há duas sessões de debate armazenadas no ViewDataDictionary.Model .
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(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());
}
// 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();
}
Quando ModelState não for válido, o mesmo ViewResult será retornado para uma solicitação GET. O teste não
tenta passar um modelo inválido. Passar um modelo inválido não é uma abordagem válida, visto que o model
binding não está em execução (embora um teste de integração use model binding). Nesse caso, o model binding
não está sendo testado. Esses testes de unidade estão testando apenas o código no método de ação.
O segundo teste verifica se, quando o ModelState é válido:
Um novo BrainstormSession é adicionado (por meio do repositório).
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 a validação fictícia no teste. Isso é realizado com a chamada a
mockRepo.Verify , que não será aprovada no teste se o método esperado não tiver sido chamado.
NOTE
A biblioteca do Moq usada neste exemplo possibilita 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.
SessionController no exemplo de aplicativo exibe informações relacionadas a uma sessão de debate específica.
O controlador inclui lógica para lidar com valores id inválidos (há dois cenários return no exemplo a seguir
para abordar esses cenários). A última instrução return retorna um novo StormSessionViewModel para a
exibição (Controllers/SessionController.cs):
return View(viewModel);
}
}
Os testes de unidade incluem um teste para cada cenário return na ação Index do controlador Session:
[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))
.ReturnsAsync((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))
.ReturnsAsync(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);
}
Mudando para o controlador Ideas, o aplicativo expõe a funcionalidade como uma API Web na rota api/ideas :
Uma lista de ideias ( IdeaDTO ) associada com uma sessão de debate é retornada pelo método ForSession .
O método Create adiciona novas ideias a uma sessão.
[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);
}
Evite retornar entidades de domínio de negócios diretamente por meio de chamadas à API. Entidades de
domínio:
Geralmente incluem mais dados do que o cliente necessita.
Acople desnecessariamente o modelo de domínio interno do aplicativo à API exposta publicamente.
É possível executar o mapeamento entre entidades de domínio e os tipos retornados ao cliente:
Manualmente com um LINQ Select , como o aplicativo de exemplo usa. Para saber mais, consulte LINQ
(Consulta Integrada à Linguagem).
Automaticamente com uma biblioteca, como AutoMapper.
Em seguida, o aplicativo de exemplo demonstra os testes de unidade para os métodos de API Create e
ForSession do controlador Ideas.
O aplicativo de exemplo contém dois testes ForSession . O primeiro teste determina se ForSession retorna um
NotFoundObjectResult (HTTP não encontrado) para uma sessão inválida:
[Fact]
public async Task ForSession_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSession(testSessionId);
// Assert
var notFoundObjectResult = Assert.IsType<NotFoundObjectResult>(result);
Assert.Equal(testSessionId, notFoundObjectResult.Value);
}
O segundo teste ForSession determina se ForSession retorna uma lista de ideias de sessão ( <List<IdeaDTO>> )
para uma sessão válida. As verificações também examinam a primeira ideia para confirmar se sua propriedade
Name está correta:
[Fact]
public async Task ForSession_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSession(testSessionId);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(okResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}
Para testar o comportamento do método Create quando o ModelState é inválido, o aplicativo de exemplo
adiciona um erro de modelo ao controlador como parte do teste. Não tente testar a validação de modelos ou o
model binding em testes de unidade—teste apenas o comportamento do método de ação quando houver um
ModelState inválido:
[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);
}
O segundo teste de Create depende do repositório retornar null , portanto, o repositório fictício é configurado
para retornar null . Não é necessário criar um banco de dados de teste (na memória ou de outro tipo) e
construir uma consulta que retornará esse resultado. O teste pode ser realizado em uma única instrução, como
mostrado no código de exemplo:
[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.Create(new NewIdeaModel());
// Assert
Assert.IsType<NotFoundObjectResult>(result);
}
// 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);
}
Testar ActionResult<T>
No ASP.NET Core 2.1 ou posterior, o ActionResult<T> (ActionResult<TValue>) permite que você retorne um
tipo derivado de ActionResult ou retorne um tipo específico.
O aplicativo de exemplo inclui um método que retorna um List<IdeaDTO> para uma sessão id determinada.
Se a sessão id não existir, o controlador retornará NotFound:
[HttpGet("forsessionactionresult/{sessionId}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<List<IdeaDTO>>> ForSessionActionResult(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
return result;
}
[Fact]
public async Task ForSessionActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
var nonExistentSessionId = 999;
// Act
var result = await controller.ForSessionActionResult(nonExistentSessionId);
// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
// Act
var result = await controller.ForSessionActionResult(testSessionId);
// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(actionResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}
O aplicativo de exemplo também inclui um método para criar um novo Idea para uma determinada sessão. O
controlador retorna:
BadRequest para um modelo inválido.
NotFound se a sessão não existir.
CreatedAtAction quando a sessão for atualizada com a nova ideia.
[HttpPost("createactionresult")]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<ActionResult<BrainstormSession>> CreateActionResult([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (session == null)
{
return NotFound(model.SessionId);
}
await _sessionRepository.UpdateAsync(session);
// Act
var result = await controller.CreateActionResult(model: null);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}
[Fact]
public async Task CreateActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var nonExistentSessionId = 999;
string testName = "test name";
string testDescription = "test description";
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
var createdAtActionResult = Assert.IsType<CreatedAtActionResult>(actionResult.Result);
var returnValue = Assert.IsType<BrainstormSession>(createdAtActionResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnValue.Ideas.Count());
Assert.Equal(testName, returnValue.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnValue.Ideas.LastOrDefault().Description);
}
Recursos adicionais
Testes de integração no ASP.NET Core
Crie e execute testes de unidade com o Visual Studio.
Princípio de Dependências Explícitas
Testes de integração no ASP.NET Core
12/01/2019 • 33 minutes to read • Edit Online
NOTE
Para SPAs de teste, é recomendável uma ferramenta, como Selenium, que pode automatizar um navegador.
TIP
Não escreva testes de integração para cada permutação possíveis de acesso de dados e arquivos com sistemas de
arquivos e bancos de dados. Independentemente de quantos coloca em um aplicativo de interagir com bancos de dados
e sistemas de arquivos, um conjunto com foco de leitura, gravação, atualização e exclusão integração testes são
geralmente capazes de banco de dados de teste adequadamente e componentes do sistema de arquivos. Testes de
unidade de uso para testes de rotina da lógica do método que interagem com esses componentes. Em testes de
unidade, o uso da infra-estrutura de falsificações/simulações resultado na execução de teste mais rápido.
NOTE
Em discussões de testes de integração, o projeto testado com frequência é chamado de sistema em teste, ou "SUT" de
forma abreviada.
NOTE
Ao criar um projeto de teste para um aplicativo, separe os testes de unidade dos testes de integração em projetos
diferentes. Isso ajuda a garantir que o teste componentes da infraestrutura não acidentalmente incluídos nos testes de
unidade. Também permite a separação dos testes de unidade e integração de controle sobre qual conjunto de testes são
executados.
Não há praticamente nenhuma diferença entre a configuração para testes de aplicativos de páginas do Razor e
aplicativos MVC. A única diferença está em como os testes são nomeados. Em um aplicativo de páginas do
Razor, os testes de pontos de extremidade de página geralmente são nomeadas depois a classe de modelo de
página (por exemplo, IndexPageTests para testar a integração do componente para a página de índice). Em um
aplicativo MVC, testes são geralmente organizadas por classes de controlador e os controladores de testam
por eles em homenagem (por exemplo, HomeControllerTests para testar a integração do componente para o
controlador Home).
Teste as classes implementam uma acessório de classe interface (IClassFixture) para indicar a classe contém
testes e fornece instâncias de objeto compartilhado entre os testes na classe.
Teste básico de pontos de extremidade do aplicativo
O seguinte teste de classe, BasicTests , usa o WebApplicationFactory para inicializar o SUT e fornecer uma
HttpClient para um método de teste, Get_EndpointsReturnSuccessAndCorrectContentType . O método verifica se o
código de status de resposta for bem-sucedida (códigos de status no intervalo 200-299) e o Content-Type
cabeçalho é text/html; charset=utf-8 para várias páginas de aplicativo.
CreateClient cria uma instância de HttpClient que segue redirecionamentos e manipula cookies
automaticamente.
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
// Act
var response = await client.GetAsync("/SecurePage");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.StartsWith("http://localhost/Identity/Account/Login",
response.Headers.Location.OriginalString);
}
Não permitindo que o cliente para seguir o redirecionamento, podem ser feitas as seguintes verificações:
O código de status retornado pelo SUT pode ser verificado em relação a esperada HttpStatusCode.Redirect
resultado, não o código de status final após o redirecionamento para a página de logon, o que seria
Httpstatuscode.
O Location valor de cabeçalho nos cabeçalhos de resposta é verificado para confirmar que ela começa
com http://localhost/Identity/Account/Login , não a logon página resposta final, onde o Location
cabeçalho não está presente.
Para obter mais informações sobre WebApplicationFactoryClientOptions , consulte o opções de cliente seção.
Personalizar WebApplicationFactory
Configuração do host da Web pode ser criada independentemente das classes de teste herdando de
WebApplicationFactory para criar um ou mais fábricas personalizadas:
try
{
// Seed the database with test data.
Utilities.InitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the " +
"database with test messages. Error: {ex.Message}");
}
}
});
}
}
public IndexPageTests(
CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
}
[Fact]
public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
{
// Arrange
var defaultPage = await _client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
//Act
var response = await _client.SendAsync(
(IHtmlFormElement)content.QuerySelector("form[id='messages']"),
(IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
// Assert
Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}
Todas as solicitações POST para o SUT devem satisfazer a verificação de antifalsificação é feita
automaticamente pelo aplicativo do sistema de proteção de dados antifalsificação. Para organizar para
solicitação POST de um teste, o aplicativo de teste deve:
1. Faça uma solicitação para a página.
2. Analise o cookie antifalsificação e o token de validação de solicitação da resposta.
3. Fazer a solicitação POST com a validação antifalsificação do cookie e solicitação de token em vigor.
O SendAsync métodos de extensão auxiliar (Helpers/HttpClientExtensions.cs) e o GetDocumentAsync método
auxiliar (Helpers/HtmlHelpers.cs) no doaplicativodeexemplo usar o AngleSharp analisador para lidar com a
verificação de antiforgery com os seguintes métodos:
GetDocumentAsync – Recebe o HttpResponseMessage e retorna um IHtmlDocument . GetDocumentAsync usa
uma fábrica que prepara uma resposta virtual com base no original HttpResponseMessage . Para obter mais
informações, consulte o AngleSharp documentação.
SendAsync métodos de extensão para o HttpClient compor uma HttpRequestMessage e chame
SendAsync(HttpRequestMessage) para enviar solicitações para o SUT. Sobrecargas SendAsync aceite o
formulário HTML ( IHtmlFormElement ) e o seguinte:
Enviar o botão do formulário ( IHtmlElement )
Coleção de valores de formulário ( IEnumerable<KeyValuePair<string, string>> )
Botão enviar ( IHtmlElement ) e valores de formulário ( IEnumerable<KeyValuePair<string, string>> )
NOTE
AngleSharp é um terceiro biblioteca usada para fins de demonstração neste tópico e o aplicativo de exemplo de análise.
AngleSharp não é suportado ou necessários para os testes de integração de aplicativos ASP.NET Core. Outros
analisadores podem usados, como o Pack de agilidade de Html (HAP). Outra abordagem é escrever código para lidar
com o token de verificação de solicitação e o cookie antifalsificação o sistema antifalsificação diretamente.
try
{
Utilities.InitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred seeding " +
"the database with test messages. Error: " +
ex.Message);
}
}
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
var defaultPage = await client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
//Act
var response = await client.SendAsync(
(IHtmlFormElement)content.QuerySelector("form[id='messages']"),
(IHtmlButtonElement)content.QuerySelector("button[id='deleteBtn1']"));
// Assert
Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}
Opções do cliente
A tabela a seguir mostra o padrão WebApplicationFactoryClientOptions disponíveis ao criar HttpClient
instâncias.
_client = _factory.CreateClient(clientOptions);
O exemplo SUT inclui um serviço com escopo definido que retorna uma citação. A cotação é inserida em um
campo oculto na página de índice quando a página de índice é solicitada.
Services/IQuoteService.cs:
Services/QuoteService.cs:
// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
public Task<string> GenerateQuote()
{
return Task.FromResult<string>(
"Come on, Sarah. We've an appointment in London, " +
"and we're already 30,000 years late.");
}
}
Startup.cs:
services.AddScoped<IQuoteService, QuoteService>();
Pages/Index.cshtml.cs:
[BindProperty]
public Message Message { get; set; }
[TempData]
public string MessageAnalysisResult { get; set; }
Pages/Index.cs:
Para testar a injeção de serviço e as aspas em um teste de integração, um serviço de simulação é injetado no
SUT pelo teste. O serviço fictício substitui o aplicativo QuoteService com um serviço fornecido pelo aplicativo
de teste, chamado TestQuoteService :
IntegrationTests.IndexPageTests.cs:
[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddScoped<IQuoteService, TestQuoteService>();
});
})
.CreateClient();
//Act
var defaultPage = await client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
var quoteElement = content.QuerySelector("#quote");
// Assert
Assert.Equal("Something's interfering with time, Mr. Scarman, " +
"and time is my business.", quoteElement.Attributes["value"].Value);
}
A marcação produzida durante a execução do teste reflete o texto de cotação fornecido pelo TestQuoteService ,
portanto, os passos de asserção:
...
});
}
...
});
}
}
{
"shadowCopy": false
}
Descarte de objetos
Após os testes do IClassFixture implementação são executadas, TestServer e HttpClient são descartados
quando xUnit descarta o WebApplicationFactory . Se objetos instanciados pelo desenvolvedor exigem
disposição, descartá-los no IClassFixture implementação. Para obter mais informações, consulte
implementando um método Dispose.
Os testes podem ser executados usando os recursos de teste interno de um IDE, como Visual Studio. Se
usando Visual Studio Code ou a linha de comando, execute o seguinte comando em um prompt de comando
na tests/RazorPagesProject.Tests pasta:
dotnet test
É a estrutura de teste xUnit. Testes de integração são realizados usando o testhost, que inclui o TestServer.
Porque o Microsoft.AspNetCore.Mvc.Testing pacote é usado para configurar o servidor de host e o teste de
teste, o TestHost e TestServer pacotes não exigem referências de pacote direto no arquivo de projeto de teste
do aplicativo ou configuração de desenvolvedor do aplicativo de teste.
A propagação do banco de dados para teste
Testes de integração geralmente exigem um pequeno conjunto de dados no banco de dados antes da execução
de teste. Por exemplo, uma exclusão chamadas de teste para uma exclusão de registros de banco de dados,
portanto, o banco de dados deve ter pelo menos um registro para a solicitação de exclusão seja bem-sucedida.
O aplicativo de exemplo propaga o banco de dados com três mensagens na Utilities.cs que testes podem usar
quando elas são executadas:
Recursos adicionais
Testes de unidade
Testes de unidades de páginas Razor
Middleware
Controladores de teste
ASP.NET Core teste de estresse e carregar
11/01/2019 • 4 minutes to read • Edit Online
Teste de carga e testes de estresse são importantes para garantir que um aplicativo web é eficaz e escalonável. Suas
metas são diferentes, mesmo que eles compartilham muitas vezes testes semelhantes.
Testes de carga: Testa se o aplicativo pode lidar com uma carga especificada de usuários para um determinado
cenário e ainda assim satisfazer a meta de resposta. O aplicativo é executado em condições normais.
Testes de estresse: Estabilidade do aplicativo de testes ao executar sob condições extremas e muitas vezes um
longo período de tempo:
Carga de usuário com altos – picos ou aumentando gradualmente.
Recursos de computação limitados.
Sob carga excessiva, pode o aplicativo se recuperar de falha e normalmente retornar ao comportamento esperado?
Sob carga excessiva, o aplicativo está não executado sob condições normais.
DevOps do Azure
Execuções de teste de carga podem ser iniciadas usando o planos de teste do Azure DevOps service.
O serviço suporta os seguintes tipos de formato de teste:
Teste do Visual Studio – teste da web criado no Visual Studio.
Teste com base em arquivo HTTP – tráfego HTTP capturado dentro do arquivo morto é reproduzida durante o
teste.
Teste com base na URL – permite especificar URLs para carregar testes, tipos de solicitação, cabeçalhos e
cadeias de caracteres de consulta. Executar a configuração de parâmetros, como duração, padrão de carga, o
número de usuários, etc., pode ser configurado.
Apache JMeter de teste.
Portal do Azure
Portal do Azure permite configurar e executar testes de carga de aplicativos Web, diretamente a partir da guia de
desempenho do serviço de aplicativo no portal do Azure.
O teste pode ser um teste manual com uma URL especificada, ou um arquivo de teste do Visual Studio Web, que
pode testar várias URLs.
No final do teste, os relatórios são gerados para mostrar as características de desempenho do aplicativo.
Estatísticas de exemplo incluem:
Tempo médio de resposta
Taxa de transferência máxima: solicitações por segundo
Percentual de falha
Ferramentas de terceiros
A lista a seguir contém as ferramentas de desempenho da web de terceiros com vários conjuntos de recursos:
Apache JMeter : Pacote de destaque completo de ferramentas de teste de carga. Limite de thread: precisa de um
thread por usuário.
AB - servidor HTTP Apache ferramenta de benchmark
Gatling : Ferramenta da área de trabalho com gravadores de GUI e de teste. Mais eficaz do que o JMeter.
Locust.IO : Não é limitado por threads.
Recursos adicionais
Série de blogs de teste de carga por Charles Sterling. Com data, mas a maioria dos tópicos ainda é relevante.
Solucionar problemas de projetos do ASP.NET Core
28/11/2018 • 6 minutes to read • Edit Online
As versões de bit 32 e 64 do SDK do .NET Core são instaladas. Somente modelos das versões de 64 bits
instalados em ' c:\Program Files\dotnet\sdk\' será exibida.
Esse aviso é exibido quando (x86) 32 bits e 64 bits (x64) versões dos SDK do .NET Core estão instalados. Ambas
as versões podem ser instaladas os motivos comuns incluem:
Você originalmente baixou o instalador do SDK do .NET Core usando um computador de 32 bits, mas, em
seguida, copiado entre e instalado em um computador de 64 bits.
O .NET Core SDK de 32 bits foi instalado por outro aplicativo.
A versão incorreta foi baixada e instalada.
Desinstale o .NET Core SDK 32 bits para evitar esse aviso. Desinstalar do painel de controle > programas e
recursos > desinstalar ou alterar um programa. Se você entender por que o aviso ocorre e suas implicações,
você pode ignorar o aviso.
SDK do .NET Core é instalado em vários locais
No novo projeto caixa de diálogo para o ASP.NET Core, você poderá ver o seguinte aviso:
SDK do .NET Core é instalado em vários locais. Somente modelos dos SDKs instalados em ' c:\Program
Files\dotnet\sdk\' será exibida.
Você verá esta mensagem quando você tem pelo menos uma instalação do SDK do .NET Core em um diretório
fora do c:\arquivos de programas\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 .NET Core SDK 32 bits para evitar esse aviso. Desinstalar do painel de controle > programas e
recursos > desinstalar ou alterar um programa. Se você entender por que o aviso ocorre e suas implicações,
você pode ignorar o aviso.
Não há SDKs do .NET Core foram detectados
No novo projeto caixa de diálogo para o ASP.NET Core, você poderá ver o seguinte aviso:
Nenhum SDK do .NET Core foi detectado, verifique se que eles estão incluídos na variável de ambiente
'PATH'.
Esse aviso é exibido quando a variável de ambiente PATH não apontar para qualquer SDKs do .NET Core no
computador. Para resolver esse problema:
Instale ou verifique se o que SDK do .NET Core é instalado.
Verifique o PATH variável de ambiente aponta para o local em que o SDK está instalado. O instalador
normalmente define o PATH .
sb.Append($"Request{rule}");
sb.Append($"{DateTimeOffset.Now}{nl}");
sb.Append($"{context.Request.Method} {context.Request.Path}{nl}");
sb.Append($"Scheme: {context.Request.Scheme}{nl}");
sb.Append($"Host: {context.Request.Headers["Host"]}{nl}");
sb.Append($"PathBase: {context.Request.PathBase.Value}{nl}");
sb.Append($"Path: {context.Request.Path.Value}{nl}");
sb.Append($"Query: {context.Request.QueryString.Value}{nl}{nl}");
sb.Append($"Connection{rule}");
sb.Append($"RemoteIp: {context.Connection.RemoteIpAddress}{nl}");
sb.Append($"RemotePort: {context.Connection.RemotePort}{nl}");
sb.Append($"LocalIp: {context.Connection.LocalIpAddress}{nl}");
sb.Append($"LocalPort: {context.Connection.LocalPort}{nl}");
sb.Append($"ClientCert: {context.Connection.ClientCertificate}{nl}{nl}");
sb.Append($"Identity{rule}");
sb.Append($"User: {context.User.Identity.Name}{nl}");
var scheme = await authSchemeProvider
.GetSchemeAsync(IISDefaults.AuthenticationScheme);
sb.Append($"DisplayName: {scheme?.DisplayName}{nl}{nl}");
sb.Append($"Headers{rule}");
foreach (var header in context.Request.Headers)
{
sb.Append($"{header.Key}: {header.Value}{nl}");
}
sb.Append(nl);
sb.Append($"Websockets{rule}");
if (context.Features.Get<IHttpUpgradeFeature>() != null)
{
sb.Append($"Status: Enabled{nl}{nl}");
}
else
{
sb.Append($"Status: Disabled{nl}{nl}");
}
sb.Append($"Configuration{rule}");
foreach (var pair in config.AsEnumerable())
{
sb.Append($"{pair.Path}: {pair.Value}{nl}");
}
sb.Append(nl);
sb.Append($"Environment Variables{rule}");
var vars = System.Environment.GetEnvironmentVariables();
foreach (var key in vars.Keys.Cast<string>().OrderBy(key => key,
StringComparer.OrdinalIgnoreCase))
{
var value = vars[key];
sb.Append($"{key}: {value}{nl}");
}
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(sb.ToString());
});
}
}
Registro em log no ASP.NET Core
04/02/2019 • 47 minutes to read • Edit Online
Adicionar provedores
Um provedor de log exibe ou armazena logs. Por exemplo, o provedor de Console exibe os logs no console,
e o provedor do Azure Application Insights armazena-os no Azure Application Insights. Os logs podem ser
enviados para vários destinos por meio da adição de vários provedores.
Para adicionar um provedor, chame o método de extensão Add{provider name} do provedor no Program.cs:
webHost.Run();
}
O modelo de projeto padrão chama o método de extensão CreateDefaultBuilder, que adiciona os seguintes
provedores de log:
Console
Depurar
EventSource (a partir do ASP.NET Core 2.2)
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
Se você usar CreateDefaultBuilder , poderá substituir os provedores padrão por aqueles que preferir.
Chame ClearProviders e adicione os provedores desejados.
host.Run();
}
Para usar um provedor, instale o pacote NuGet e chame o método de extensão do provedor em uma
instância de ILoggerFactory:
NOTE
O aplicativo de exemplo adiciona provedores de log no método Startup.Configure . Para obter saída de log do
código que é executado anteriormente, adicione provedores de log no construtor de classe Startup .
Saiba mais sobre provedores de log internos e provedores de log de terceiros mais adiante no artigo.
Criar logs
Obtenha um objeto ILogger<TCategoryName> da DI.
O exemplo de controlador a seguir cria os logs Information e Warning . A categoria é
TodoApiSample.Controllers.TodoController (o nome de classe totalmente qualificado do TodoController no
aplicativo de exemplo):
O exemplo do Razor Pages a seguir cria logs com Information como o nível e
TodoApiSample.Pages.AboutModel como a categoria:
O exemplo acima cria logs com Information e Warning como o nível e a classe TodoController como a
categoria.
O nível de log indica a gravidade do evento registrado. A categoria do log é uma cadeia de caracteres
associada a cada log. A instância ILogger<T> cria logs que têm o nome totalmente qualificado do tipo T
como a categoria. Níveis e categorias serão explicados com mais detalhes posteriormente neste artigo.
Criar logs na inicialização
Para gravar logs na classe Startup , inclua um parâmetro ILogger na assinatura de construtor:
public class Startup
{
private readonly ILogger _logger;
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
}
host.Run();
}
Configuração
A configuração do provedor de logs é fornecida por um ou mais provedores de sincronização:
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).
Por exemplo, a configuração de log geralmente é fornecida pela seção Logging dos arquivos de
configurações do aplicativo. O exemplo a seguir mostra o conteúdo de um típico arquivo
appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
},
"Console":
{
"IncludeScopes": true
}
}
}
A propriedade Logging pode ter LogLevel e propriedades do provedor de logs (o Console é mostrado).
A propriedade LogLevel em Logging especifica o nível mínimo para log nas categorias selecionadas. No
exemplo, as categorias System e Microsoft têm log no nível Information , e todas as outras no nível Debug
.
Outras propriedades em Logging especificam provedores de logs. O exemplo se refere ao provedor de
Console. Se um provedor oferecer suporte a escopos de log, IncludeScopes indicará se eles estão
habilitados. Uma propriedade de provedor (como Console , no exemplo) também pode especificar uma
propriedade LogLevel . LogLevel em um provedor especifica os níveis de log para esse provedor.
Se os níveis forem especificados em Logging.{providername}.LogLevel , eles substituirão o que estiver
definido em Logging.LogLevel .
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
Chaves LogLevel representam nomes de log. A chave Default aplica-se a logs não listados de forma
explícita. O valor representa o nível de log aplicado ao log fornecido.
Saiba mais sobre como implementar provedores de configuração em Configuração no ASP.NET Core.
Os logs anteriores foram gerados por meio de uma solicitação HTTP Get para o aplicativo de exemplo em
http://localhost:5000/api/todo/0 .
Veja 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 de código da
estrutura ASP.NET Core. O ASP.NET Core e o código do aplicativo estão usando a mesma API de registro
em log e os mesmos provedores.
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
Quando um objeto ILogger é criado, uma categoria é especificada para ele. Essa categoria é incluída em
cada mensagem de log criada por essa instância de Ilogger . A categoria pode ser qualquer cadeia de
caracteres, mas a convenção é usar o nome da classe, como "TodoApi.Controllers.TodoController".
Use ILogger<T> para obter uma instância ILogger que usa o nome de tipo totalmente qualificado do T
como a categoria:
public class TodoController : Controller
{
private readonly ITodoRepository _todoRepository;
private readonly ILogger _logger;
Nível de log
Todo log especifica um valor LogLevel. O nível de log indica a gravidade ou importância. Por exemplo, você
pode gravar um log Information quando um método é finalizado normalmente e um log Warning quando
um método retorna um código de status 404 Não Encontrado.
O código a seguir cria os logs Information e Warning :
public IActionResult GetById(string id)
{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
var item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
return new ObjectResult(item);
}
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.
Use 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, envie Trace por meio do nível Information para um armazenamento de dados com
volume. Envie Warning por meio de Critical para um armazenamento de dados com valor.
Durante o desenvolvimento, envie Warning por meio Critical para o console e adicione Trace por
meio de Information ao solucionar problemas.
A seção Filtragem de log mais adiante neste artigo explicará como controlar os níveis de log que um
provedor manipula.
O ASP.NET Core grava logs para eventos de estrutura. Os exemplos de log anteriores neste artigo
excluíram logs abaixo do nível Information , portanto, logs de nível Debug ou Trace não foram criados.
Veja um exemplo de logs de console produzidos por meio da execução do aplicativo de exemplo
configurado para mostrar logs Debug :
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
Cada log pode especificar uma ID do evento. O aplicativo de exemplo faz isso usando uma classe
LoggingEvents definida localmente:
Uma ID de evento associa um conjunto de eventos. Por exemplo, todos os logs relacionados à exibição de
uma lista de itens em uma página podem ser 1001.
O provedor de logs pode armazenar a ID do evento em um campo de ID na mensagem de log ou não
armazenar. O provedor de Depuração não mostra IDs de eventos. O provedor de console mostra IDs de
evento entre colchetes após a categoria:
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. No código a seguir, observe que os nomes de parâmetro estão fora de sequência no
modelo de mensagem:
string p1 = "parm1";
string p2 = "parm2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);
Esse código cria uma mensagem de log com os valores de parâmetro na sequência:
A estrutura de registros funciona dessa maneira para que os provedores de logs possam implementar
registro em log semântico, também conhecido como registro em log estruturado. Os próprios argumentos
são passados para o sistema de registro em log, não apenas o modelo de mensagem formatado. Essas
informações permitem que os provedores de log armazenem os valores de parâmetro como campos. Por
exemplo, suponha que as chamadas de método do agente sejam assim:
_logger.LogInformation("Getting item {ID} at {RequestTime}", id, DateTime.Now);
Se você estiver enviando os logs para o Armazenamento de Tabelas do Azure, cada entidade da Tabela do
Azure poderá ter propriedades ID e RequestTime , o que simplificará as consultas nos dados de log. Uma
consulta pode encontrar todos os logs em determinado intervalo de RequestTime sem 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
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.
Para suprimir todos os logs, especifique LogLevel.None como o nível de log mínimo. O valor inteiro de
LogLevel.None é 6, que é maior do que LogLevel.Critical (5 ).
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": {
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"IncludeScopes": false,
"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 para todos os provedores. Apenas uma regra é escolhida para cada provedor quando um objeto
ILogger é criado.
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 um objeto ILogger é criado, o objeto ILoggerFactory seleciona uma única regra por provedor
para aplicar a esse agente. Todas as mensagens gravadas pela instância 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 correspondência 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 correspondência 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 .
Com a lista anterior de regras, suponha que você crie 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.
A instância ILogger resultante envia logs de nível Trace e superior para o provedor Depuração. Logs de
nível Debug e superior são enviados para o provedor Console.
Aliases de provedor
Cada provedor define um alias que pode ser usado na configuração no lugar do nome de tipo totalmente
qualificado. 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
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. Por exemplo:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logBuilder =>
{
logBuilder.AddFilter((provider, category, logLevel) =>
{
if (provider == "Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider" &&
category == "TodoApiSample.Controllers.TodoController")
{
return false;
}
return true;
});
})
.Build();
Alguns provedores de log permitem especificar quando os logs devem ser gravados em uma mídia de
armazenamento ou ignorados, com base no nível de log e na categoria.
Os métodos de extensão AddConsole e AddDebug fornecem sobrecargas que aceitam critérios de filtragem.
O código de exemplo a seguir faz com que o provedor de console ignore os logs abaixo do nível Warning ,
enquanto o provedor Depuração ignora os logs criados pela estrutura.
O método AddEventLog tem uma sobrecarga que recebe uma instância EventLogSettings , que pode conter
uma função de filtragem em sua propriedade Filter . O provedor TraceSource não fornece nenhuma
dessas sobrecargas, porque seu nível de registro em log e outros parâmetros são baseados no
SourceSwitch e no TraceListener que ele usa.
Para definir regras de filtragem para todos os provedores registrados com uma instância ILoggerFactory ,
use o método de extensão WithFilter . O exemplo abaixo limita os logs de estrutura (categoria começa com
"Microsoft" ou "Sistema") a avisos, enquanto registra em nível de depuração os logs criados por código de
aplicativo.
Para impedir a gravação de logs, especifique LogLevel.None como o nível de log mínimo. O valor inteiro de
LogLevel.None é 6, que é maior do que LogLevel.Critical (5 ).
CATEGORIA OBSERVAÇÕES
Escopos de log
Um escopo pode agrupar um conjunto de operações lógicas. Esse agrupamento pode ser usado para anexar
os mesmos dados para cada log criado como parte de um conjunto. Por exemplo, todo log criado como
parte do processamento de uma transação pode incluir a ID da transação.
Um escopo é um tipo IDisposable retornado pelo método BeginScope e que dura até que seja descartado.
Use um escopo por meio do encapsulamento de chamadas de agente em um bloco using :
Program.cs:
NOTE
A configuração da opção de agente de console IncludeScopes é necessária para habilitar o registro em log baseado
em escopo.
Startup.cs:
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
logging.AddConsole();
loggerFactory.AddConsole();
As sobrecargas do AddConsole permitem que você passe um nível de log mínimo, uma função de filtro e
um valor booliano que indica se escopos são compatíveis. Outra opção é passar um objeto IConfiguration ,
que pode especificar suporte de escopos e níveis de log.
O provedor de console tem um impacto significativo no desempenho e geralmente não é adequado para
uso em produção.
Quando você cria um novo projeto no Visual Studio, o método AddConsole tem essa aparência:
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
{
"Logging": {
"Console": {
"IncludeScopes": false
},
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
As configurações mostradas limitam os logs de estrutura a avisos, permitindo que o aplicativo para faça
registros no nível de depuração, conforme explicado na Filtragem de log seção. Para obter mais
informações, consulte Configuração.
Para ver a saída de registro em log de console, abra um prompt de comando na pasta do projeto e execute o
seguinte comando:
dotnet run
Depurar provedor
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.
logging.AddDebug();
loggerFactory.AddDebug();
As sobrecargas de AddDebug permitem que você passe um nível de log mínimo ou uma função de filtro.
Provedor EventSource
Para aplicativos que se destinam ao ASP.NET Core 1.1.0 ou posterior, 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.
logging.AddEventSourceLogger();
loggerFactory.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).
logging.AddEventLog();
loggerFactory.AddEventLog();
As sobrecargas de AddEventLog permitem que você passe EventLogSettings ou um nível de log mínimo.
Provedor TraceSource
O pacote de provedor Microsoft.Extensions.Logging.TraceSource usa as bibliotecas e provedores de
TraceSource.
logging.AddTraceSource(sourceSwitchName);
loggerFactory.AddTraceSource(sourceSwitchName);
logging.AddAzureWebAppDiagnostics();
loggerFactory.AddAzureWebAppDiagnostics();
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 saber mais sobre o comportamento padrão, confira
AzureAppServicesDiagnosticsSettings.
O provedor funciona somente quando o projeto é executado no ambiente do Azure. Ele não tem nenhum
efeito quando o projeto é executado localmente—ele não grava em arquivos locais ou no armazenamento
de desenvolvimento local para blobs.
Fluxo de log do Azure
O fluxo de log do Azure permite que você exiba a atividade de log em tempo real:
O servidor de aplicativos
Do servidor Web
De uma solicitação de rastreio com falha
Para configurar o fluxo de log do Azure:
Navegue até a página Logs de Diagnóstico da página do portal do seu aplicativo.
Defina o Log de aplicativo (Sistema de Arquivos) como Ativado.
Navegue até a página Fluxo de Log para exibir as mensagens de aplicativo. Elas são registradas pelo
aplicativo por meio da interface ILogger .
Log de rastreamento do Azure Application Insights
O SDK do Application Insights pode coletar e relatar logs gerados por meio da infraestrutura de log do
ASP.NET Core. Para obter mais informações, consulte os seguintes recursos:
Visão geral do Application Insights
Application Insights para ASP.NET Core
Application Insights logging adapters (Adaptadores de registro em log do Application Insights).
Recursos adicionais
Registro em log de alto desempenho com o LoggerMessage no ASP.NET Core
Páginas Razor do ASP.NET Core com EF Core – série
de tutoriais
11/07/2018 • 2 minutes to read • Edit Online
Esta série de tutoriais ensina a criar aplicativos Web de Razor Pages do ASP.NET Core que usam o EF (Entity
Framework) Core para o acesso a dados.
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
Páginas Razor com o Entity Framework Core no
ASP.NET Core – Tutorial 1 de 8
16/01/2019 • 28 minutes to read • Edit Online
A versão deste tutorial para o ASP.NET Core 2.0 pode ser encontrada neste arquivo PDF.
A versão deste tutorial para o ASP.NET Core 2.1 contém diversas melhorias em relação à versão 2.0.
Por Tom Dykstra e Rick Anderson
O aplicativo Web de exemplo Contoso University demonstra como criar um aplicativo Razor Pages do
ASP.NET Core usando o EF (Entity Framework) Core.
O aplicativo de exemplo é um site de uma Contoso University fictícia. Ele inclui funcionalidades como
admissão de alunos, criação de cursos e atribuições de instrutor. Esta página é a primeira de uma série de
tutoriais que explica como criar o aplicativo de exemplo Contoso University.
Baixe ou exiba o aplicativo concluído. Instruções de download.
Pré-requisitos
Visual Studio
CLI do .NET Core
Visual Studio 2017 versão 15.7.3 ou posterior com as cargas de trabalho a seguir:
ASP.NET e desenvolvimento para a Web
Desenvolvimento entre plataformas do .NET Core
SDK do .NET Core 2.1 ou posteriores
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 projeto concluído. Uma boa maneira de obter ajuda é postando uma pergunta no
StackOverflow.com sobre o ASP.NET Core ou EF Core.
<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>
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>
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. Em classnameID , classname é o nome da classe. A chave primária alternativa reconhecida
automaticamente é StudentID no exemplo anterior.
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.
Conclua a caixa de diálogo Adicionar Razor Pages usando o Entity Framework (CRUD ):
Na lista suspensa classe Modelo, selecione Aluno (ContosoUniversity.Models).
Na linha Classe de contexto de dados, selecione o sinal de (mais) + e altere o nome gerado para
ContosoUniversity.Models.SchoolContext.
Na lista suspensa Classe de contexto de dados, selecione
ContosoUniversity.Models.SchoolContext
Selecione Adicionar.
Confira Fazer scaffold do modelo de filme se tiver problemas na etapa anterior.
O processo de scaffold criou e alterou os seguintes arquivos:
Arquivos criados
Pages/Students Criar, Excluir, Detalhes, Editar, Índice.
Data/SchoolContext.cs
Atualizações de arquivo
Startup.cs: alterações a esse arquivo serão detalhadas na próxima seção.
appsettings.json: a cadeia de conexão usada para se conectar a um banco de dados local é adicionada.
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}
O nome da cadeia de conexão é passado para o contexto com a chamada de um método em um objeto
DbContextOptions. Para o desenvolvimento local, o sistema de configuração do ASP.NET Core lê a cadeia
de conexão do arquivo appsettings.json.
Atualizar o principal
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 EnsureCreated.
Descarte o contexto quando o método EnsureCreated for concluído.
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
try
{
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
host.Run();
}
EnsureCreated garante que o banco de dados do contexto exista. Se ele existir, nenhuma ação será
realizada. Se ele não existir, o banco de dados e todos os seus esquemas serão criados. EnsureCreated não
usa migrações para criar o banco de dados. Um banco de dados criado com EnsureCreated não pode ser
atualizado posteriormente usando migrações.
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 .
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Models
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options)
: base(options)
{
}
O código destacado cria uma propriedade DbSet<TEntity> 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 .
SQL Server Express LocalDB
A cadeia de conexão especifica um LocalDB do SQL Server. LocalDB é uma versão leve do Mecanismo de
Banco de Dados do SQL Server Express destinado ao desenvolvimento de aplicativos, e não ao uso em
produção. O LocalDB é iniciado sob demanda e executado no modo de usuário e, portanto, não há
nenhuma configuração complexa. Por padrão, o LocalDB cria arquivos .mdf de BD no diretório
C:/Users/<user> .
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Models
{
public static class DbInitializer
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// context.Database.EnsureCreated();
try
{
var context = services.GetRequiredService<SchoolContext>();
// using ContosoUniversity.Data;
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
host.Run();
}
Exclua todos os registros de alunos e reinicie o aplicativo. Se o BD não for inicializado, defina um ponto de
interrupção em Initialize para diagnosticar o problema.
Exibir o BD
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.
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.
A versão deste tutorial para o ASP.NET Core 2.0 pode ser encontrada neste arquivo PDF.
A versão deste tutorial para o ASP.NET Core 2.1 contém diversas melhorias em relação à versão 2.0.
Por Tom Dykstra, Jon P Smith e Rick Anderson
O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Neste tutorial, o código CRUD (criar, ler, atualizar e excluir) gerado por scaffolding é examinado e personalizado.
Para minimizar a complexidade e manter o foco destes tutoriais no EF Core, o código do EF Core é usado nos
modelos de página. Alguns desenvolvedores usam um padrão de repositório ou de camada de serviço para criar
uma camada de abstração entre a interface do usuário (Páginas do Razor) e a camada de acesso a dados.
Neste tutorial, as Razor Pages Criar, Editar, Excluir e Detalhes na pasta Student são examinadas.
O código gerado por scaffolding usa o seguinte padrão para as páginas Criar, Editar e Excluir:
Obtenha e exiba os dados solicitados com o método HTTP GET OnGetAsync .
Salve as alterações nos dados com o método HTTP POST OnPostAsync .
As páginas Índice e Detalhes obtêm e exibem os dados solicitados com o método HTTP GET OnGetAsync
FindAsync
Em grande parte do código gerado por scaffolding, FindAsync pode ser usado no lugar de FirstOrDefaultAsync .
FindAsync :
Encontra uma entidade com o PK (chave primária). Se uma entidade com o PK está sendo controlada pelo
contexto, ela é retornada sem uma solicitação para o BD.
É simples e conciso.
É otimizado para pesquisar uma única entidade.
Pode ter benefícios de desempenho em algumas situações, mas isso é raro em aplicativos Web.
Usa FirstAsync em vez de SingleAsync de forma implícita.
Mas se você deseja Include outras entidades, FindAsync não é mais apropriado. Isso significa que talvez seja
necessário abandonar FindAsync e passar para uma consulta à medida que o aplicativo avança.
<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:
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 de dados relacionado à leitura.
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.Student.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return null;
}
TryUpdateModelAsync
Examine o código TryUpdateModelAsync:
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 SaveChangesAsync
é chamado. Por exemplo, quando uma nova entidade é passada para o método AddAsync, o estado da entidade é
definido como Added. Quando SaveChangesAsync é 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.Student.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, 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
Alunos/Índice ou outros links não funcionam:
Verifique se a Página do Razor contém a diretiva @page correta. Por exemplo, a Razor Page Alunos/Índice não
deve conter um modelo de rota:
@page "{id:int}"
A N T E R IO R P R Ó X IM O
Páginas Razor com o EF Core no ASP.NET Core –
Classificação, filtro, paginação – 3 de 8
30/01/2019 • 25 minutes to read • Edit Online
A versão deste tutorial para o ASP.NET Core 2.0 pode ser encontrada neste arquivo PDF.
A versão deste tutorial para o ASP.NET Core 2.1 contém diversas melhorias em relação à versão 2.0.
Por Tom Dykstra, Rick Anderson e Jon P Smith
O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Neste tutorial, as funcionalidades de classificação, filtragem, agrupamento e paginação são adicionadas.
A ilustração a seguir mostra uma página concluída. Os títulos de coluna são links clicáveis para classificar a
coluna. Clicar em um título de coluna alterna repetidamente entre a ordem de classificação ascendente e
descendente.
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído.
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 Students/Index.cshtml.cs, defina um ponto de interrupção em switch (sortOrder) .
Adicione uma inspeção para NameSort e DateSort .
Em Students/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 ordenação 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 à página Student Index
Em Pages/Students/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 .
Modificar a Página Sobre do Razor
Substitua o código no arquivo Pages/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.
A N T E R IO R P R Ó X IM O
Páginas Razor com o EF Core no ASP.NET Core –
Migrações – 4 de 8
16/07/2018 • 10 minutes to read • Edit Online
A versão deste tutorial para o ASP.NET Core 2.0 pode ser encontrada neste arquivo PDF.
A versão deste tutorial para o ASP.NET Core 2.1 contém diversas melhorias em relação à versão 2.0.
Por Tom Dykstra, Jon P Smith e Rick Anderson
O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Neste tutorial, o recurso de migrações do EF Core para o gerenciamento de alterações do modelo de dados é
usado.
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído.
Quando um novo aplicativo é desenvolvido, o modelo de dados é alterado com frequência. Sempre que o modelo
é alterado, ele fica fora de sincronia com o banco de dados. Este tutorial começa configurando o Entity
Framework para criar o banco de dados, caso ele não exista. Sempre que o modelo de dados é alterado:
O BD é removido.
O EF cria um novo que corresponde ao modelo.
O aplicativo propaga o BD com os dados de teste.
Essa abordagem para manter o BD em sincronia com o modelo de dados funciona bem até que você implante o
aplicativo em produção. Quando o aplicativo é executado em produção, normalmente, ele armazena dados que
precisam ser mantidos. O aplicativo não pode começar com um BD de teste sempre que uma alteração é feita
(como a adição de uma nova coluna). O recurso Migrações do EF Core resolve esse problema, permitindo que o
EF Core atualize o esquema de BD em vez de criar um novo BD.
Em vez de remover e recriar o BD quando o modelo de dados é alterado, as migrações atualizam o esquema e
retêm os dados existentes.
Drop-Database
Add-Migration InitialCreate
Update-Database
migrationBuilder.CreateTable(
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Enrollment");
migrationBuilder.DropTable(
name: "Course");
migrationBuilder.DropTable(
name: "Student");
}
}
As migrações chamam o método Up para implementar as alterações do modelo de dados para uma migração.
Quando você insere um comando para reverter a atualização, as migrações chamam o método Down .
O código anterior refere-se à migração inicial. Esse código foi criado quando o comando
migrations add InitialCreate foi executado. O parâmetro de nome da migração ("InitialCreate" no exemplo) é
usado para o nome do arquivo. O nome da migração pode ser qualquer nome de arquivo válido. É melhor
escolher uma palavra ou frase que resume o que está sendo feito na migração. Por exemplo, uma migração que
adicionou uma tabela de departamento pode ser chamada "AddDepartmentTable".
Se a migração inicial foi criada e o BD existe:
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 for executado, ele não fará 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, o BD foi removido, e não existe mais. Então, as migrações criam o novo BD.
O instantâneo do modelo de dados
As migrações criam um instantâneo do esquema de banco de dados atual em
Migrations/SchoolContextModelSnapshot.cs. Quando você adiciona uma migração, o EF determina o que foi
alterado, comparando o modelo de dados com o arquivo de instantâneo.
Para excluir uma migração, use o seguinte comando:
Visual Studio
CLI do .NET Core
Remove-Migration
O comando de exclusão de migrações exclui a migração e garante que o instantâneo seja redefinido
corretamente.
Remover EnsureCreated e testar o aplicativo
Para o desenvolvimento inicial, EnsureCreated foi usado. Neste tutorial, as migrações são usadas. EnsureCreated
tem as seguintes limitações:
Ignora as migrações e cria o BD e o esquema.
Não cria uma tabela de migrações.
Não pode ser usado com migrações.
Foi projetado para teste ou criação rápida de protótipos em que o BD é removido e recriado com frequência.
Remova a seguinte linha de DbInitializer :
context.Database.EnsureCreated();
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
Baixar o aplicativo concluído.
O aplicativo gera a seguinte exceção:
Recursos adicionais
CLI do .NET Core.
Console do Gerenciador de Pacotes (Visual Studio)
A N T E R IO R P R Ó X IM O
Páginas Razor com o EF Core no ASP.NET Core –
Modelo de dados – 5 de 8
10/01/2019 • 47 minutes to read • Edit Online
A versão deste tutorial para o ASP.NET Core 2.0 pode ser encontrada neste arquivo PDF.
A versão deste tutorial para o ASP.NET Core 2.1 contém diversas melhorias em relação à versão 2.0.
Por Tom Dykstra e Rick Anderson
O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Os tutoriais anteriores trabalharam com um modelo de dados básico composto por três entidades. Neste tutorial:
Mais entidades e relações são adicionadas.
O modelo de dados é personalizado com a especificação das regras de formatação, validação e mapeamento
de banco de dados.
As classes de entidade para o modelo de dados concluído são mostradas na seguinte ilustração:
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído.
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:
Add-Migration ColumnFirstName
Update-Database
O comando migrations add ColumnFirstName gera a seguinte mensagem de aviso:
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 .
NOTE
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.
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 :
O atributo Display
O atributo Display especifica que a legenda para as caixas de texto deve ser "Nome", "Sobrenome", "Nome
Completo" e "Data de Registro". As legendas padrão não tinham nenhum espaço entre as palavras, por exemplo,
"Lastname".
A propriedade calculada FullName
FullName é uma propriedade calculada que retorna um valor criado pela concatenação de duas outras
propriedades. FullName não pode ser definido; ele apenas tem um acessador get. Nenhuma coluna FullName é
criada no banco de dados.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
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.
[Key]
public int InstructorID { get; set; }
Por padrão, o EF Core trata a chave como não gerada pelo banco de dados porque a coluna destina-se a uma
relação de identificação.
A propriedade de navegação Instructor
A propriedade de navegação OfficeAssignment da entidade Instructor permite valor nulo porque:
Tipos de referência (como classes que permitem valor nulo).
Um instrutor pode não ter uma atribuição de escritório.
A entidade OfficeAssignment tem uma propriedade de navegação Instructor que não permite valor nulo
porque:
InstructorIDnã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.Models
{
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.Student.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. Consulte Enrollments e
CourseAssignments para obter exemplos de como tabelas de junção muitos para muitos podem ser propagadas.
Add-Migration ComplexDataModel
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'.
Aplicar a migração
Agora que você tem um banco de dados existente, precisa pensar sobre como aplicar as alterações futuras a ele.
Este tutorial mostra duas abordagens:
Remover e recriar o banco de dados
Aplicar a migração ao banco de dados existente. Embora esse método seja mais complexo e demorado, é a
abordagem preferencial para ambientes de produção do mundo real. Observação: essa é uma seção opcional
do tutorial. Você pode remover e recriar etapas e ignorar esta seção. Se você quiser seguir as etapas nesta
seção, não realize as etapas de remover e recriar.
Remover e recriar o banco de dados
O código no DbInitializer atualizado adiciona dados de semente às novas entidades. Para forçar o EF Core a
criar um novo BD, remova e atualize o BD:
Visual Studio
CLI do .NET Core
No PMC (Console do Gerenciador de Pacotes), execute o seguinte comando:
Drop-Database
Update-Database
Abra o BD no SSOX:
Se o SSOX for aberto anteriormente, clique no botão Atualizar.
Expanda o nó Tabelas. As tabelas criadas são exibidas.
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-csharp]
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 .
A N T E R IO R P R Ó X IM O
Páginas Razor com o EF Core no ASP.NET Core –
Ler dados relacionados – 6 de 8
30/10/2018 • 24 minutes to read • Edit Online
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. O carregamento lento foi adicionado ao EF Core na versão 2.1. 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 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 Pages/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="Create">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 ao 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; }
}
}
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 de um o instrutor apenas com a diretiva @page 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);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
}
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
Páginas Razor com o EF Core no ASP.NET Core –
Atualizar dados relacionados – 7 de 8
30/10/2018 • 23 minutes 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");}
}
A página contém um campo oculto ( <input type="hidden"> ) para o número do curso. A adição de um auxiliar de
marcação <label> com asp-for="Course.CourseID" não elimina a necessidade do campo oculto.
<input type="hidden"> é necessário para que o número seja incluído nos dados postados quando o usuário clicar
em Salvar.
Teste o código atualizado. Crie, edite e exclua um curso.
[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");}
}
NOTE
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");
}
}
}
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:
SET NOCOUNT ON;
UPDATE [Department] SET [Name] = @p0
WHERE [DepartmentID] = @p1 AND [RowVersion] = @p2;
SELECT [RowVersion]
FROM [Department]
WHERE @@ROWCOUNT = 1 AND [DepartmentID] = @p1;
O código anterior realçado mostra a cláusula WHERE que contém RowVersion . Se o BD RowVersion não for igual
ao parâmetro RowVersion ( @p2 ), nenhuma linha será atualizada.
O seguinte código realçado mostra o T-SQL que verifica exatamente se uma linha foi atualizada:
@@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");
});
@{
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>
A N T E R IO R
ASP.NET Core MVC com EF Core – série de tutoriais
21/06/2018 • 2 minutes 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.
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
ASP.NET Core MVC com o Entity Framework Core –
tutorial – 1 de 10
10/01/2019 • 40 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à
versão 2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e
exibições. O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
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.
O aplicativo Web de exemplo Contoso University demonstra como criar aplicativos Web ASP.NET Core 2.0
MVC usando o EF (Entity Framework) Core 2.0 e o Visual Studio 2017.
O aplicativo de exemplo é um site de uma Contoso University fictícia. Ele inclui funcionalidades como admissão
de alunos, criação de cursos e atribuições de instrutor. Este é o primeiro de uma série de tutoriais que explica
como criar o aplicativo de exemplo Contoso University do zero.
Baixe ou exiba o aplicativo concluído.
O EF Core 2.0 é a última versão do EF, mas ainda não tem todos os recursos do EF 6.x. Para obter informações
sobre como escolher entre o EF 6.x e o EF Core, consulte EF Core vs. EF6.x. Se você escolher o EF 6.x, confira a
versão anterior desta série de tutoriais.
NOTE
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.
Pré-requisitos
Clique em uma das seguintes opções:
Ferramentas da CLI: Windows, Linux ou macOS: SDK 2.0 ou posterior do .NET Core
Ferramentas de editor/IDE
Windows: Visual Studio para Windows
Carga de trabalho ASP.NET e desenvolvimento para a Web
Carga de trabalho de desenvolvimento multiplataforma do .NET Core
Linux: Visual Studio Code
macOS: Visual Studio para Mac
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.
TIP
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.
<!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>
</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"
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. Esse pacote está incluído no metapacote
Microsoft.AspNetCore.App, portanto você não precisa referenciar o pacote se o aplicativo tem uma referência
de pacote ao pacote Microsoft.AspNetCore.App .
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 ).
A propriedade CourseID é uma chave estrangeira e a propriedade de navegação correspondente é Course .
Uma entidade Enrollment está associada a uma entidade Course .
O Entity Framework interpreta uma propriedade como uma propriedade de chave estrangeira se ela é
nomeada <navigation property name><primary key property name> (por exemplo, StudentID para a propriedade
de navegação Student , pois a chave primária da entidade Student é ID ). As propriedades de chave
estrangeira também podem ser nomeadas apenas <primary key property name> (por exemplo, CourseID , pois a
chave primária da entidade Course é CourseID ).
A entidade Course
Na pasta Models, crie Course.cs e substitua o código existente pelo seguinte código:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
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.
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 Core é 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.
A VA N ÇA R
ASP.NET Core MVC com EF Core – CRUD – 2 de 10
01/11/2018 • 38 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à versão
2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições.
O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
No tutorial anterior, você criou um aplicativo MVC que armazena e exibe dados usando o Entity Framework e o
LocalDB do SQL Server. Neste tutorial, você examinará e personalizará o código CRUD (criar, ler, atualizar e
excluir) que o scaffolding do MVC cria automaticamente para você em controladores e exibições.
NOTE
É 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 Ler 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 os auxiliares de marca, confira Auxiliares de Marca 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:
Esse código adiciona a entidade Student criada pelo associador de modelos do ASP.NET Core 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 Core MVC que facilita o trabalho com os dados enviados por um formulário. Um
associador de modelos converte os valores de formulário postados em tipos CLR e passa-os para o método de
ação em parâmetros. Nesse caso, o associador de modelos cria uma instância de uma entidade Student usando
valores de propriedade da coleção Form.)
Você removeu ID do atributo Bind porque a ID é o valor de chave primária que o SQL Server definirá
automaticamente quando a linha for inserida. A entrada do usuário não define o valor da ID.
Além do atributo Bind , o bloco try-catch é a única alteração que você fez no código gerado por scaffolding. Se
uma exceção que é derivada de DbUpdateException é capturada enquanto as alterações estão sendo salvas, uma
mensagem de erro genérica é exibida. Às vezes, as exceções DbUpdateException são causadas por algo externo ao
aplicativo, em vez de por um erro de programação e, portanto, o usuário é aconselhado a tentar novamente.
Embora não implementado nesta amostra, um aplicativo de qualidade de produção registrará a exceção em log.
Para obter mais informações, consulte a seção Log para informações em Monitoramento e telemetria (criando
aplicativos de nuvem do mundo real com o Azure).
O atributo ValidateAntiForgeryToken ajuda a impedir ataques CSRF (solicitação intersite forjada). O token é
injetado automaticamente na exibição pelo FormTagHelper e é incluído quando o formulário é enviado pelo
usuário. O token é validado pelo atributo ValidateAntiForgeryToken . Para obter mais informações sobre o CSRF,
consulte Falsificação antissolicitação.
Observação de segurança sobre o excesso de postagem
O atributo Bind que o código gerado por scaffolding inclui no método Create é uma maneira de proteger
contra o excesso de postagem em cenários de criação. Por exemplo, suponha que a entidade Student inclua uma
propriedade Secret que você não deseja que essa página da Web defina.
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}
Mesmo se você não tiver um campo Secret na página da Web, um invasor poderá usar uma ferramenta como o
Fiddler ou escrever um JavaScript para postar um valor de formulário Secret . Sem o atributo Bind limitando
os campos que o associador de modelos usa quando ele cria uma instância de Student, o associador de modelos
seleciona esse valor de formulário Secret e usa-o para criar a instância da entidade Student. Em seguida, seja
qual for o valor que o invasor especificou para o campo de formulário Secret , ele é atualizado no banco de
dados. A imagem a seguir mostra a ferramenta Fiddler adicionando o campo Secret (com o valor "OverPost")
aos valores de formulário postados.
Em seguida, o valor "OverPost" é adicionado com êxito à propriedade Secret da linha inserida, embora você
nunca desejou que a página da Web pudesse definir essa propriedade.
Impeça o excesso de postagem em cenários de edição lendo a entidade do banco de dados primeiro e, em
seguida, chamando TryUpdateModel , passando uma lista explícita de propriedades permitidas. Esse é o método
usado nestes tutoriais.
Uma maneira alternativa de impedir o excesso de postagem preferida por muitos desenvolvedores é usar
modelos de exibição em vez de classes de entidade com o model binding. 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 o model binding. 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:
. A entidade ainda não existe no banco de dados. O método
Added 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.
A N T E R IO R P R Ó X IM O
ASP.NET Core MVC com EF Core – classificação,
filtro, paginação – 3 de 10
16/07/2018 • 27 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à versão
2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições.
O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
No tutorial anterior, você implementou um conjunto de páginas da Web para operações CRUD básicas para
entidades Student. Neste tutorial você adicionará as funcionalidades de classificação, filtragem e paginação à
página Índice de Alunos. Você também criará uma página que faz um agrupamento simples.
A ilustração a seguir mostra a aparência da página quando você terminar. Os títulos de coluna são links que o
usuário pode clicar para classificar por essa coluna. Clicar em um título de coluna alterna repetidamente entre a
ordem de classificação ascendente e descendente.
Adicionar links de classificação de coluna à página Índice de Alunos
Para adicionar uma classificação à página Índice de Alunos, você alterará o método Index do controlador Alunos
e adicionará o código à exibição Índice de Alunos.
Adicionar a funcionalidade de classificação ao método Index
Em StudentsController.cs, substitua o método Index pelo seguinte código:
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).
NOTE
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 .
NOTE
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
ASP.NET Core MVC com EF Core – migrações – 4
de 10
01/11/2018 • 15 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à
versão 2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e
exibições. O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
Neste tutorial, você começa usando o recurso de migrações do EF Core para o gerenciamento de alterações do
modelo de dados. Em tutoriais seguintes, você adicionará mais migrações conforme você alterar o modelo de
dados.
Introdução às migrações
Quando você desenvolve um novo aplicativo, o modelo de dados é alterado com frequência e, sempre que o
modelo é alterado, ele fica fora de sincronia com o banco de dados. Você começou estes tutoriais configurando
o Entity Framework para criar o banco de dados, caso ele não exista. Em seguida, sempre que você alterar o
modelo de dados – adicionar, remover, alterar classes de entidade ou alterar a classe DbContext –, poderá excluir
o banco de dados e o EF criará um novo que corresponde ao modelo e o propagará com os dados de teste.
Esse método de manter o banco de dados em sincronia com o modelo de dados funciona bem até que você
implante o aplicativo em produção. Quando o aplicativo é executado em produção, ele normalmente armazena
os dados que você deseja manter, e você não quer perder tudo sempre que fizer uma alteração, como a adição
de uma nova coluna. O recurso Migrações do EF Core resolve esse problema, permitindo que o EF atualize o
esquema de banco de dados em vez de criar um novo banco de dados.
<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 as migrações, mas você verá mais tarde o motivo pelo qual essa é uma boa ideia.
NOTE
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'
NOTE
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.
O instantâneo do modelo de dados
As migrações criam um instantâneo do esquema de banco de dados atual em
Migrations/SchoolContextModelSnapshot.cs. Quando você adiciona uma migração, o EF determina o que foi
alterado, comparando o modelo de dados com o arquivo de instantâneo.
Ao excluir uma migração, use o comando dotnet ef migrations remove. dotnet ef migrations remove exclui a
migração e garante que o instantâneo seja redefinido corretamente.
Confira Migrações do EF Core em ambientes de equipe para obter mais informações de como o arquivo de
instantâneo é usado.
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 Registro em log no ASP.NET Core.
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.
CLI (interface de linha de comando) vs. PMC (Console do
Gerenciador de Pacotes)
As ferramentas do EF para gerenciamento de migrações estão disponíveis por meio dos comandos da CLI do
.NET Core ou de cmdlets do PowerShell na janela PMC (Console do Gerenciador de Pacotes) do Visual Studio.
Este tutorial mostra como usar a CLI, mas você poderá usar o PMC se preferir.
Os comandos do EF para os comandos do PMC estão no pacote Microsoft.EntityFrameworkCore.Tools. Esse
pacote está incluído no metapacote Microsoft.AspNetCore.App, portanto você não precisa adicionar uma
referência de pacote se o aplicativo tem uma referência de pacote ao Microsoft.AspNetCore.App .
Importante: esse não é o mesmo pacote que é instalado para a CLI com a edição do arquivo .csproj. O nome
deste termina com Tools , ao contrário do nome do pacote da CLI que termina com Tools.DotNet .
Para obter mais informações sobre os comandos da CLI, consulte CLI do .NET Core.
Para obter mais informações sobre os comandos do PMC, consulte Console do Gerenciador de Pacotes (Visual
Studio).
Resumo
Neste tutorial, você viu como criar e aplicar sua primeira migração. No próximo tutorial, você começará
examinando tópicos mais avançados com a expansão do modelo de dados. Ao longo do processo, você criará e
aplicará migrações adicionais.
A N T E R IO R P R Ó X IM O
ASP.NET Core MVC com EF Core – modelo de
dados – 5 de 10
30/10/2018 • 53 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à
versão 2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições.
O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
Nos tutoriais anteriores, você trabalhou com um modelo de dados simples composto por três entidades. Neste
tutorial, você adicionará mais entidades e relações e personalizará o modelo de dados especificando formatação,
validação e regras de mapeamento de banco de dados.
Quando terminar, as classes de entidade formarão o modelo de dados concluído mostrado na seguinte
ilustração:
Personalizar o modelo de dados usando atributos
Nesta seção, você verá como personalizar o modelo de dados usando atributos que especificam formatação,
validação e regras de mapeamento de banco de dados. Em seguida, em várias seções a seguir, você criará o
modelo de dados Escola completo com a adição de atributos às classes já criadas e criação de novas classes para
os demais tipos de entidade no modelo.
O atributo DataType
Para datas de registro de alunos, todas as páginas da Web atualmente exibem a hora junto com a data, embora
tudo o que você deseje exibir nesse campo seja a data. Usando atributos de anotação de dados, você pode fazer
uma alteração de código que corrigirá o formato de exibição em cada exibição que mostra os dados. Para ver um
exemplo de como fazer isso, você adicionará um atributo à propriedade EnrollmentDate na classe Student .
Em Models/Student.cs, adicione uma instrução using ao namespace System.ComponentModel.DataAnnotations e
adicione os atributos DataType e DisplayFormat à EnrollmentDate propriedade, conforme mostrado no seguinte
exemplo:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
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 Core 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.
NOTE
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.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
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; }
NOTE
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.
A N T E R IO R P R Ó X IM O
ASP.NET Core MVC com o EF Core – ler dados
relacionados – 6 de 10
16/07/2018 • 26 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à
versão 2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições.
O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
No tutorial anterior, você concluiu o modelo de dados Escola. Neste tutorial, você lerá e exibirá dados
relacionados – ou seja, os dados que o Entity Framework carrega nas propriedades de navegação.
As ilustrações a seguir mostram as páginas com as quais você trabalhará.
Carregamento adiantado, explícito e lento de dados relacionados
Há várias maneiras pelas quais um software ORM (Object-Relational Mapping), como o Entity Framework, pode
carregar dados relacionados nas propriedades de navegação de uma entidade:
Carregamento adiantado. Quando a entidade é lida, os dados relacionados são recuperados com ela.
Normalmente, isso resulta em uma única consulta de junção que recupera todos os dados necessários.
Especifique o carregamento adiantado no Entity Framework Core usando os métodos Include e
ThenInclude .
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
30/10/2018 • 32 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à versão
2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições.
O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
No tutorial anterior, você exibiu dados relacionados; neste tutorial, você atualizará dados relacionados pela
atualização dos campos de chave estrangeira e das propriedades de navegação.
As ilustrações a seguir mostram algumas das páginas com as quais você trabalhará.
Personalizar as páginas Criar e Editar dos cursos
Quando uma nova entidade de curso é criada, ela precisa ter uma relação com um departamento existente. Para
facilitar isso, o código gerado por scaffolding inclui métodos do controlador e exibições Criar e Editar que incluem
uma lista suspensa para seleção do departamento. A lista suspensa define a propriedade de chave estrangeira
Course.DepartmentID , e isso é tudo o que o Entity Framework precisa para carregar a propriedade de navegação
Department com a entidade Department apropriada. Você usará o código gerado por scaffolding, mas o alterará
ligeiramente para adicionar tratamento de erro e classificação à lista suspensa.
Em CoursesController.cs, exclua os quatro métodos Create e Edit e substitua-os pelo seguinte código:
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 do model binding. 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.
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);
}
}
}
}
NOTE
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.
NOTE
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
30/10/2018 • 33 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à versão
2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições.
O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
Nos tutoriais anteriores, você aprendeu a atualizar dados. Este tutorial mostra como lidar com conflitos quando
os mesmos usuários atualizam a mesma entidade simultaneamente.
Você criará páginas da Web que funcionam com a entidade Department e tratará erros de simultaneidade. As
ilustrações a seguir mostram as páginas Editar e Excluir, incluindo algumas mensagens exibidas se ocorre um
conflito de simultaneidade.
Conflitos de simultaneidade
Um conflito de simultaneidade ocorre quando um usuário exibe dados de uma entidade para editá-los e, em
seguida, outro usuário atualiza os mesmos dados da entidade antes que a primeira alteração do usuário seja
gravada no banco de dados. Se você não habilitar a detecção desses conflitos, a última pessoa que atualizar o
banco de dados substituirá as outras alterações do usuário. Em muitos aplicativos, esse risco é aceitável: se
houver poucos usuários ou poucas atualizações ou se não for realmente crítico se algumas alterações forem
substituídas, o custo de programação para simultaneidade poderá superar o benefício. Nesse caso, você não
precisa configurar o aplicativo para lidar com conflitos de simultaneidade.
Simultaneidade pessimista (bloqueio )
Se o aplicativo precisar evitar a perda acidental de dados em cenários de simultaneidade, uma maneira de fazer
isso será usar bloqueios de banco de dados. Isso é chamado de simultaneidade pessimista. Por exemplo, antes de
ler uma linha de um banco de dados, você solicita um bloqueio para o acesso somente leitura ou de atualização.
Se você bloquear uma linha para o acesso de atualização, nenhum outro usuário terá permissão para bloquear a
linha para o acesso somente leitura ou de atualização, porque ele obterá uma cópia dos dados que estão sendo
alterados. Se você bloquear uma linha para o acesso somente leitura, outros também poderão bloqueá-la para o
acesso somente leitura, mas não para atualização.
O gerenciamento de bloqueios traz desvantagens. Ele pode ser complexo de ser programado. Exige recursos de
gerenciamento de banco de dados significativos e pode causar problemas de desempenho, conforme o número
de usuários de um aplicativo aumenta. Por esses motivos, nem todos os sistemas de gerenciamento de banco de
dados dão suporte à simultaneidade pessimista. O Entity Framework Core não fornece nenhum suporte interno
para ele, e este tutorial não mostra como implementá-lo.
Simultaneidade otimista
A alternativa à simultaneidade pessimista é a simultaneidade otimista. Simultaneidade otimista significa permitir
que conflitos de simultaneidade ocorram e responder adequadamente se eles ocorrerem. Por exemplo, Alice visita
a página Editar Departamento e altera o valor do Orçamento para o departamento de inglês de US$ 350.000,00
para US$ 0,00.
Antes que Alice clique em Salvar, Julio visita a mesma página e altera o campo Data de Início de 1/9/2007 para
1/9/2013.
Alice clica em Salvar primeiro e vê a alteração quando o navegador retorna à página Índice.
Em seguida, Julio clica em Salvar em uma página Editar que ainda mostra um orçamento de US$ 350.000,00. O
que acontece em seguida é determinado pela forma como você lida com conflitos de simultaneidade.
Algumas das opções incluem o seguinte:
Controle qual propriedade um usuário modificou e atualize apenas as colunas correspondentes no banco
de dados.
No cenário de exemplo, nenhum dado é perdido, porque propriedades diferentes foram atualizadas pelos
dois usuários. Na próxima vez que alguém navegar pelo departamento de inglês, verá as alterações de
Alice e de Julio – a data de início de 1/9/2013 e um orçamento de zero dólar. Esse método de atualização
pode reduzir a quantidade de conflitos que podem resultar em perda de dados, mas ele não poderá evitar a
perda de dados se forem feitas alterações concorrentes à mesma propriedade de uma entidade. Se o Entity
Framework funciona dessa maneira depende de como o código de atualização é implementado.
Geralmente, isso não é prático em um aplicativo Web, porque pode exigir que você mantenha grandes
quantidades de estado para manter o controle de todos os valores de propriedade originais de uma
entidade, bem como novos valores. A manutenção de grandes quantidades de estado pode afetar o
desempenho do aplicativo, pois exige recursos do servidor ou deve ser incluída na própria página da Web
(por exemplo, em campos ocultos) ou em um cookie.
Você não pode deixar a alteração de Julio substituir a alteração de Alice.
Na próxima vez que alguém navegar pelo departamento de inglês, verá 1/9/2013 e o valor de US$
350.000,00 restaurado. Isso é chamado de um cenário O cliente vence ou O último vence. (Todos os
valores do cliente têm precedência sobre o conteúdo do armazenamento de dados.) Conforme observado
na introdução a esta seção, se você não fizer nenhuma codificação para a manipulação de simultaneidade,
isso ocorrerá automaticamente.
Você pode impedir que as alterações de Julio sejam atualizadas no banco de dados.
Normalmente, você exibirá uma mensagem de erro, mostrará a ele o estado atual dos dados e permitirá a
ele aplicar as alterações novamente se ele ainda desejar fazê-las. Isso é chamado de um cenário O
armazenamento vence. (Os valores do armazenamento de dados têm precedência sobre os valores
enviados pelo cliente.) Você implementará o cenário O armazenamento vence neste tutorial. Esse método
garante que nenhuma alteração é substituída sem que um usuário seja alertado sobre o que está
acontecendo.
Detectando conflitos de simultaneidade
Resolva conflitos manipulando exceções DbConcurrencyException geradas pelo Entity Framework. Para saber
quando gerar essas exceções, o Entity Framework precisa poder detectar conflitos. Portanto, é necessário
configurar o banco de dados e o modelo de dados de forma adequada. Algumas opções para habilitar a detecção
de conflitos incluem as seguintes:
Na tabela de banco de dados, inclua uma coluna de acompanhamento que pode ser usada para determinar
quando uma linha é alterada. Em seguida, configure o Entity Framework para incluir essa coluna na
cláusula Where de comandos SQL Update ou Delete.
O tipo de dados da coluna de acompanhamento é normalmente rowversion . O valor rowversion é um
número sequencial que é incrementado sempre que a linha é atualizada. Em um comando Update ou
Delete, a cláusula Where inclui o valor original da coluna de acompanhamento (a versão de linha original).
Se a linha que está sendo atualizada tiver sido alterada por outro usuário, o valor da coluna rowversion
será diferente do valor original; portanto, a instrução Update ou Delete não poderá encontrar a linha a ser
atualizada devido à cláusula Where. Quando o Entity Framework descobre que nenhuma linha foi
atualizada pelo comando Update ou Delete (ou seja, quando o número de linhas afetadas é zero), ele
interpreta isso como um conflito de simultaneidade.
Configure o Entity Framework para incluir os valores originais de cada coluna na tabela na cláusula Where
de comandos Update e Delete.
Como a primeira opção, se nada na linha for alterado desde que a linha tiver sido lida pela primeira vez, a
cláusula Where não retornará uma linha a ser atualizada, o que o Entity Framework interpretará como um
conflito de simultaneidade. Para tabelas de banco de dados que têm muitas colunas, essa abordagem pode
resultar em cláusulas Where muito grandes e pode exigir que você mantenha grandes quantidades de
estado. Conforme observado anteriormente, manter grandes quantidades de estado pode afetar o
desempenho do aplicativo. Portanto, essa abordagem geralmente não é recomendada e não é o método
usado neste tutorial.
Caso deseje implementar essa abordagem, precisará marcar todas as propriedades de chave não primária
na entidade para as quais você deseja controlar a simultaneidade adicionando o atributo ConcurrencyCheck
a elas. Essa alteração permite que o Entity Framework inclua todas as colunas na cláusula Where do SQL
de instruções Update e Delete.
No restante deste tutorial, você adicionará uma propriedade de acompanhamento rowversion à entidade
Department, criará um controlador e exibições e testará para verificar se tudo está funcionando corretamente.
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
30/10/2018 • 15 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à
versão 2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições.
O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte
primeiro tutorial na série.
No tutorial anterior, você tratou exceções de simultaneidade. Este tutorial mostrará como implementar a herança
no modelo de dados.
Na programação orientada a objeto, você pode usar a herança para facilitar a reutilização de código. Neste
tutorial, você alterará as classes Instructor e Student , de modo que elas derivem de uma classe base Person
que contém propriedades, como LastName , comuns a instrutores e alunos. Você não adicionará nem alterará as
páginas da Web, mas alterará uma parte do código, e essas alterações serão refletidas automaticamente no
banco de dados.
Opções para o mapeamento de herança para as tabelas de banco de
dados
As classes Instructor e Student no modelo de dados Escola têm várias propriedades idênticas:
Suponha que você deseje eliminar o código redundante para as propriedades compartilhadas pelas entidades
Instructor e Student . Ou você deseja escrever um serviço que pode formatar nomes sem se preocupar se o
nome foi obtido de um instrutor ou um aluno. Você pode criar uma classe base Person que contém apenas
essas propriedades compartilhadas e, em seguida, fazer com que as classes Instructor e Student herdem
dessa classe base, conforme mostrado na seguinte ilustração:
Há várias maneiras pelas quais essa estrutura de herança pode ser representada no banco de dados. Você pode
ter uma tabela Person que inclui informações sobre alunos e instrutores em uma única tabela. Algumas das
colunas podem se aplicar somente a instrutores (HireDate), algumas somente a alunos (EnrollmentDate) e
outras a ambos (LastName, FirstName). Normalmente, você terá uma coluna discriminatória para indicar qual
tipo cada linha representa. Por exemplo, a coluna discriminatória pode ter "Instrutor" para instrutores e "Aluno"
para alunos.
Esse padrão de geração de uma estrutura de herança de entidade com base em uma tabela de banco de dados
individual é chamado de herança TPH (tabela por hierarquia).
Uma alternativa é fazer com que o banco de dados se pareça mais com a estrutura de herança. Por exemplo,
você pode ter apenas os campos de nome na tabela Pessoa e as tabelas separadas Instrutor e Aluno com os
campos de data.
Esse padrão de criação de uma tabela de banco de dados para cada classe de entidade é chamado de herança
TPT (tabela por tipo).
Outra opção é mapear todos os tipos não abstratos para tabelas individuais. Todas as propriedades de uma
classe, incluindo propriedades herdadas, são mapeadas para colunas da tabela correspondente. Esse padrão é
chamado de herança TPC (Tabela por Classe Concreta). Se você tiver implementado a herança TPC para as
classes Person, Student e Instructor, conforme mostrado anteriormente, as tabelas Aluno e Instrutor não
parecerão diferentes após a implementação da herança do que antes.
Em geral, os padrões de herança TPC e TPH oferecem melhor desempenho do que os padrões de herança TPT,
porque os padrões TPT podem resultar em consultas de junção complexas.
Este tutorial demonstra como implementar a herança TPH. TPH é o único padrão de herança compatível com o
Entity Framework Core. O que você fará é criar uma classe Person , alterar as classes Instructor e Student
para que elas derivem de Person , adicionar a nova classe ao DbContext e criar uma migração.
TIP
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.
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; }
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
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; }
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 });
}
}
}
Isso é tudo o que o Entity Framework precisa para configurar a herança de tabela por hierarquia. Como você
verá, quando o banco de dados for atualizado, ele terá uma tabela Pessoa no lugar das tabelas Aluno e Instrutor.
Criar e personalizar o código de migração
Salve as alterações e compile o projeto. Em seguida, abra a janela Comando na pasta do projeto e insira o
seguinte comando:
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:
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 .)
NOTE
É 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.
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
ASP.NET Core MVC com EF Core – avançado – 10
de 10
21/01/2019 • 25 minutes to read • Edit Online
Este tutorial não foi atualizado para o ASP.NET Core 2.1. A versão deste tutorial para o ASP.NET Core 2.0 pode
ser consultada selecionando ASP.NET Core 2.0 acima do sumário ou na parte superior da página:
A versão deste tutorial do Razor Pages para o ASP.NET Core 2.1 conta com várias melhorias em relação à
versão 2.0.
O tutorial do 2.0 ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições.
O tutorial do Razor Pages foi atualizado com os seguintes aprimoramentos:
É mais fácil de acompanhar. Por exemplo, o código de scaffolding foi bastante simplificado.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
Usa a API do EF Core mais recente.
Por Tom Dykstra e Rick Anderson
O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
No tutorial anterior, você implementou a herança de tabela por hierarquia. Este tutorial apresenta vários tópicos
que são úteis para consideração quando você vai além dos conceitos básicos de desenvolvimento de aplicativos
Web ASP.NET Core que usam o Entity Framework Core.
if (department == null)
{
return NotFound();
}
return View(department);
}
Para verificar se o novo código funciona corretamente, selecione a guia Departamentos e, em seguida,
Detalhes de um dos departamentos.
Chamar uma consulta que retorna outros tipos
Anteriormente, você criou uma grade de estatísticas de alunos para a página Sobre que mostrava o número de
alunos para cada data de registro. Você obteve os dados do conjunto de entidades Students ( _context.Students )
e usou o LINQ para projetar os resultados em uma lista de objetos de modelo de exibição EnrollmentDateGroup .
Suponha que você deseje gravar o próprio SQL em vez de usar LINQ. Para fazer isso, você precisa executar uma
consulta SQL que retorna algo diferente de objetos de entidade. No EF Core 1.0, uma maneira de fazer isso é
escrever um código ADO.NET e obter a conexão de banco de dados do EF.
Em HomeController.cs, substitua o método About pelo seguinte código:
public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount
= reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}
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 Core
MVC.
Para obter mais informações sobre o EF Core, consulte a documentação do Entity Framework Core. Também há
um livro disponível: Entity Framework Core in Action (Entity Framework Core em ação).
Para obter informações sobre como implantar um aplicativo Web, confira Hospedar e implantar o ASP.NET
Core.
Para obter informações sobre outros tópicos relacionados ao ASP.NET Core MVC, como autenticação e
autorização, confira Introdução ao 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:
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 a instância/o servidor especificado)
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.
A N T E R IO R
Introdução ao ASP.NET Core e ao Entity Framework 6
30/10/2018 • 7 minutes 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
Na caixa de diálogo de seleção de modelo do projeto, selecione API e .NET Framework na lista suspensa
Adicionar > Novo Projeto > Windows Desktop > 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
Use o Gulp no ASP.NET Core
28/11/2018 • 18 minutes to read • Edit Online
Gulp
O gulp é baseado em JavaScript streaming build Kit de ferramentas para o código do lado do cliente.
Normalmente, ele é usado para transmitir arquivos do lado do cliente por meio de uma série de processos
quando um evento específico é disparado em um ambiente de compilação. Por exemplo, o Gulp pode ser usado
para automatizar agrupamento e minificação ou a limpeza de um ambiente de desenvolvimento antes de uma
nova compilação.
Um conjunto de tarefas do Gulp é definido em gulpfile. js. O seguinte JavaScript 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 ser 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 criados para que os locais dos arquivos CSS e JavaScript podem ser reutilizados e referenciados
dentro das tarefas. A tabela a seguir fornece descrições dos módulos do incluídos no gulpfile. js.
NOME DO MÓDULO DESCRIÇÃO
gulp cssmin Um módulo que minimiza os arquivos CSS. Para obter mais
informações, consulte gulp cssmin.
tarefa uglify gulp Um módulo que minimiza . js arquivos. Para obter mais
informações, consulte tarefa uglify gulp.
Depois que o requisito os módulos são importados, as tarefas podem ser especificadas. Aqui, há seis tarefas
registrado, representado pelo código a seguir:
gulp.task("min:js", () => {
return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
.pipe(gulp.dest("."));
});
gulp.task("min:css", () => {
return gulp.src([paths.css, "!" + paths.minCss])
.pipe(concat(paths.concatCssDest))
.pipe(cssmin())
.pipe(gulp.dest("."));
});
A tabela a seguir fornece uma explicação sobre as tarefas especificadas no código acima:
Limpar: css Uma tarefa que usa o módulo de exclusão do nó rimraf para
remover a versão reduzida do arquivo site CSS.
{
"devDependencies": {
"gulp": "^4.0.0",
"gulp-concat": "2.6.1",
"gulp-cssmin": "0.2.0",
"gulp-uglify": "3.0.0",
"rimraf": "2.6.1"
}
}
2. Adicionar um novo arquivo JavaScript ao seu projeto e denomine gulpfile. js, em seguida, copie o código a
seguir.
/// <binding Clean='clean' />
"use strict";
const paths = {
webroot: "./wwwroot/"
};
gulp.task("min:js", () => {
return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
.pipe(gulp.dest("."));
});
gulp.task("min:css", () => {
return gulp.src([paths.css, "!" + paths.minCss])
.pipe(concat(paths.concatCssDest))
.pipe(cssmin())
.pipe(gulp.dest("."));
});
3. Na Gerenciador de soluções, clique com botão direito gulpfile. jse selecione Task Runner Explorer.
Explorador do Executador de tarefas mostra a lista de tarefas de Gulp. (Talvez você precise clicar o
Refresh botão que aparece à esquerda do nome do projeto.)
IMPORTANT
O Task Runner Explorer item de menu de contexto será exibida apenas se gulpfile. js está no diretório do projeto
raiz.
4. Sob tarefas na Task Runner Explorer, 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 limpar
conforme definido na gulpfile. js.
5. Com o botão direito do limpa da tarefa e, em seguida, selecione associações > antes da compilação.
O antes da compilação associação configura a tarefa Limpar para serem executados automaticamente
antes de cada compilação do projeto.
As associações que você configura o com Task Runner Explorer são armazenadas na forma de um comentário
na parte superior da sua gulpfile. js e entrarão em vigor apenas no Visual Studio. Uma alternativa que não requer
o Visual Studio é configurar a execução automática de tarefas de gulp em seu . csproj arquivo. Por exemplo,
colocar isso no seu . csproj arquivo:
Agora a tarefa de limpeza é executada quando você executar o projeto no Visual Studio ou de um prompt de
comando usando o execução dotnet comando (executar npm install primeiro).
Essa tarefa é denominada first , e ele simplesmente exibe uma cadeia de caracteres.
2. Salve gulpfile. js.
3. Na Gerenciador de soluções, clique com botão direito gulpfile. jse selecione Task Runner Explorer.
4. Na Task Runner Explorer, clique com botão direito primeiroe selecione executar.
O texto de saída é exibido. Para obter exemplos com base em cenários comuns, consulte receitas do Gulp.
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 para serem executados e concluídos 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. Salve gulpfile. js.
3. Na Gerenciador de soluções, clique com botão direito gulpfile. js e selecione Task Runner Explorer se
ainda não estiver aberto.
4. Na Task Runner Explorer, clique com botão direito série e selecione executar.
IntelliSense
IntelliSense fornece conclusão de código, descrições de parâmetro e outros recursos para aumentar a
produtividade e diminuir a erros. As tarefas de gulp são escritas em JavaScript; Portanto, o IntelliSense pode
fornecer assistência durante o desenvolvimento. Conforme você trabalha com JavaScript, o IntelliSense lista 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 Docs API.
Receitas do gulp
A comunidade do Gulp fornece Gulp receitas. Essas receitas consistem em tarefas de Gulp para lidar com cenários
comuns.
Recursos adicionais
Documentação do gulp
Agrupamento e minificação no ASP.NET Core
Usar o Grunt no ASP.NET Core
Use o assistente no núcleo do ASP.NET
22/06/2018 • 17 minutes 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).
NOTE
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"
}
NOTE
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.
NOTE
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'
}
},
NOTE
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,
}
},
NOTE
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:
NOTE
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
Aquisição de biblioteca do lado do cliente no
ASP.NET Core com LibMan
31/08/2018 • 2 minutes to read • Edit Online
Recursos adicionais
LibMan de uso com o ASP.NET Core no Visual Studio
Use a interface de linha de comando LibMan (CLI) com o ASP.NET Core
Repositório do GitHub do LibMan
Use a interface de linha de comando LibMan (CLI)
com o ASP.NET Core
27/09/2018 • 14 minutes to read • Edit Online
Pré-requisitos
SDK do .NET Core 2.1 ou posteriores
Instalação
Para instalar a CLI LibMan:
Um ferramenta Global do .NET Core for instalado a partir de Microsoft.Web.LibraryManager.Cli pacote do NuGet.
Para instalar a CLI LibMan de uma fonte específica de pacote do NuGet:
No exemplo anterior, uma ferramenta Global do .NET Core é instalada do computador Windows local
C:\Temp\Microsoft.Web.LibraryManager.Cli.1.0.94 -g606058a278.nupkg arquivo.
Uso
Após a instalação bem-sucedida da CLI, o comando a seguir pode ser usado:
libman
libman --version
libman --help
Options:
--help|-h Show help information
--version Show version information
Commands:
cache List or clean libman cache contents
clean Deletes all library files defined in libman.json from the project
init Create a new libman.json
install Add a library definition to the libman.json file, and download the
library to the specified location
restore Downloads all files from provider and saves them to specified
destination
uninstall Deletes all files for the specified library from their specified
destination, then removes the specified library definition from
libman.json
update Updates the specified library
Opções
As seguintes opções estão disponíveis para o libman init comando:
-d|--default-destination <PATH>
Um caminho relativo à pasta atual. Arquivos de biblioteca são instalados nesse local, se nenhum
destination propriedade está definida para uma biblioteca no libman.json. O <PATH> valor é gravado para
o defaultDestination propriedade de libman.json.
-p|--default-provider <PROVIDER>
O provedor a ser usado se nenhum provedor é definido para uma determinada biblioteca. O <PROVIDER>
valor é gravado para o defaultProvider propriedade de libman.json. Substitua <PROVIDER> com um dos
seguintes valores:
cdnjs
filesystem
unpkg
-h|--help
Exemplos
Para criar uma libman.json arquivo em um projeto ASP.NET Core:
Navegue até a raiz do projeto.
Execute o seguinte comando:
libman init
Digite o nome do provedor padrão, ou pressione Enter para usar o provedor do CDNJS padrão. Os
valores válidos incluem:
cdnjs
filesystem
unpkg
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}
Arguments
LIBRARY
O nome da biblioteca para instalar. Esse nome pode incluir a notação de número de versão (por exemplo, @1.2.0 ).
Opções
As seguintes opções estão disponíveis para o libman install comando:
-d|--destination <PATH>
O local para instalar a biblioteca. Se não for especificado, o local padrão é usado. Se nenhum
defaultDestination propriedade é especificada em libman.json, essa opção é necessária.
--files <FILE>
Especifique o nome do arquivo para instalá-lo da biblioteca. Se não for especificado, todos os arquivos da
biblioteca são instalados. Forneça um --files opção por arquivo para ser instalado. Caminhos relativos
são suportados também. Por exemplo: --files dist/browser/signalr.js .
-p|--provider <PROVIDER>
O nome do provedor a ser usado para a aquisição de biblioteca. Substitua <PROVIDER> com um dos
seguintes valores:
cdnjs
filesystem
unpkg
Se não for especificado, o defaultProvider propriedade na libman.json é usado. Se nenhum
defaultProvider propriedade é especificada em libman.json, essa opção é necessária.
-h|--help
Exemplos
Considere o seguinte libman.json arquivo:
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}
Para instalar a versão do jQuery 3.2.1 jQuery do arquivo para o wwwroot/scripts/jquery pasta usando o provedor
do CDNJS:
Opções
As seguintes opções estão disponíveis para o libman restore comando:
-h|--help
Exemplos
Para restaurar os arquivos de biblioteca definidos em libman.json:
libman restore
Excluir arquivos de biblioteca
O libman clean comando exclui os arquivos de biblioteca restaurados anteriormente por meio de LibMan. Pastas
se tornar vazias depois dessa operação são excluídas. Os arquivos de biblioteca associados configurações na
libraries propriedade de libman.json não são removidos.
Sinopse
Opções
As seguintes opções estão disponíveis para o libman clean comando:
-h|--help
Exemplos
Para excluir arquivos de biblioteca instalados por meio de LibMan:
libman clean
Arguments
LIBRARY
O nome da biblioteca para desinstalar. Esse nome pode incluir a notação de número de versão (por exemplo,
@1.2.0 ).
Opções
As seguintes opções estão disponíveis para o libman uninstall comando:
-h|--help
Exemplos
Considere o seguinte libman.json arquivo:
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "[email protected]",
"files": [
"jquery.min.js",
"jquery.js",
"jquery.min.map"
],
"destination": "wwwroot/lib/jquery/"
},
{
"provider": "unpkg",
"library": "[email protected]",
"destination": "wwwroot/lib/bootstrap/"
},
{
"provider": "filesystem",
"library": "C:\\temp\\lodash\\",
"files": [
"lodash.js",
"lodash.min.js"
],
"destination": "wwwroot/lib/lodash/"
}
]
}
Arguments
LIBRARY
Exemplos
Para atualizar o jQuery para a versão mais recente:
Arguments
PROVIDER
Usado somente com o clean comando. Especifica para limpar o cache do provedor. Os valores válidos incluem:
cdnjs
filesystem
unpkg
Opções
As seguintes opções estão disponíveis para o libman cache comando:
--files
Exemplos
Para exibir os nomes de bibliotecas em cache por um provedor, use um dos seguintes comandos:
Cache contents:
---------------
unpkg:
knockout:
<list omitted for brevity>
react:
<list omitted for brevity>
vue:
<list omitted for brevity>
cdnjs:
font-awesome
metadata.json
jquery
metadata.json
3.2.1\core.js
3.2.1\jquery.js
3.2.1\jquery.min.js
3.2.1\jquery.min.map
3.2.1\jquery.slim.js
3.2.1\jquery.slim.min.js
3.2.1\jquery.slim.min.map
3.3.1\core.js
3.3.1\jquery.js
3.3.1\jquery.min.js
3.3.1\jquery.min.map
3.3.1\jquery.slim.js
3.3.1\jquery.slim.min.js
3.3.1\jquery.slim.min.map
knockout
metadata.json
3.4.2\knockout-debug.js
3.4.2\knockout-min.js
lodash.js
metadata.json
4.17.10\lodash.js
4.17.10\lodash.min.js
react
metadata.json
Observe que a saída anterior mostra que o jQuery versões 3.2.1 e 3.3.1 são armazenados em cache no
provedor de CDNJS.
Para esvaziar o cache de biblioteca para o provedor do CDNJS:
libman cache clean cdnjs
Depois de esvaziar o cache do provedor do CDNJS o libman cache list comando exibe o seguinte:
Cache contents:
---------------
unpkg:
knockout
react
vue
cdnjs:
(empty)
Depois de esvaziar todos os caches de provedor, o libman cache list comando exibe o seguinte:
Cache contents:
---------------
unpkg:
(empty)
cdnjs:
(empty)
Recursos adicionais
Instalar uma ferramenta Global
LibMan de uso com o ASP.NET Core no Visual Studio
Repositório do GitHub do LibMan
LibMan de uso com o ASP.NET Core no Visual Studio
30/10/2018 • 15 minutes to read • Edit Online
Pré-requisitos
Visual Studio 2017 versão 15,8 ou posterior com o ASP.NET e desenvolvimento web carga de trabalho
Clique o instale botão para baixar os arquivos, de acordo com a configuração no libman.json.
Examine a Gerenciador de biblioteca feed da saída janela para obter detalhes de instalação. Por exemplo:
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "[email protected]",
"files": [
"jquery.min.js",
"jquery.js",
"jquery.min.map"
],
"destination": "wwwroot/lib/jquery/"
},
{
"provider": "unpkg",
"library": "[email protected]",
"destination": "wwwroot/lib/bootstrap/"
},
{
"provider": "filesystem",
"library": "C:\\temp\\lodash\\",
"files": [
"lodash.js",
"lodash.min.js"
],
"destination": "wwwroot/lib/lodash/"
}
]
}
NOTE
LibMan só dá suporte a uma versão de cada biblioteca de cada provedor. O libman.json arquivo Falha na validação de
esquema, se ele contiver duas bibliotecas com o mesmo nome de biblioteca para determinado provedor.
1>------ Build started: Project: LibManSample, Configuration: Debug Any CPU ------
1>
1>Restore operation started...
1>Restoring library [email protected]...
1>Restoring library [email protected]...
1>
1>2 libraries restored in 10.66 seconds
1>LibManSample -> C:\LibManSample\bin\Debug\netcoreapp2.1\LibManSample.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
Quando o comportamento de restauração no build está habilitado, o libman.json menu de contexto exibe um
desabilitar bibliotecas de cliente restaurar no Build opção. Esta opção remove o
Microsoft.Web.LibraryManager.Build referência do arquivo de projeto de pacote. Consequentemente, as bibliotecas
do lado do cliente não são restauradas em cada compilação.
Independentemente da configuração de restauração no build, você pode restaurar manualmente a qualquer
momento do libman.json menu de contexto. Para obter mais informações, consulte restaurar os arquivos
manualmente.
Restaurar os arquivos manualmente
Para restaurar manualmente os arquivos de biblioteca:
Para todos os projetos na solução:
Clique com botão direito no nome da solução no Gerenciador de soluções.
Selecione o bibliotecas de cliente restaurar opção.
Para um projeto específico:
Clique com botão direito do libman.json arquivo no Gerenciador de soluções.
Selecione o bibliotecas de cliente restaurar opção.
Enquanto a operação de restauração está em execução:
O ícone do Centro de Status da tarefa (TSC ) na barra de status do Visual Studio será animado e lerá
iniciada a operação de restauração. Clicando no ícone abre uma dica de ferramenta listando as tarefas em
segundo plano conhecidos.
As mensagens serão enviadas à barra de status e o Gerenciador de biblioteca feed da saída janela. Por
exemplo:
A operação de limpeza exclui somente os arquivos do projeto. Arquivos de biblioteca permanecem no cache para
uma recuperação mais rápida em operações de restauração futuras. Para gerenciar arquivos de biblioteca
armazenados no cache do computador local, use o LibMan CLI.
Se houver uma versão de pré-lançamento mais recente do que a versão instalada, a versão de pré-
lançamento é exibida.
Para fazer o downgrade para uma versão mais antiga da biblioteca, edite manualmente o libman.json arquivo.
Quando o arquivo é salvo, o LibMan operação de restauração:
Remove arquivos redundantes da versão anterior.
Adiciona arquivos novos e atualizados da nova versão.
Recursos adicionais
Use a interface de linha de comando LibMan (CLI) com o ASP.NET Core
Repositório do GitHub do LibMan
Gerenciar pacotes do lado do cliente com Bower no
ASP.NET Core
13/11/2018 • 10 minutes to read • Edit Online
IMPORTANT
Enquanto o Bower é mantido, seus mantenedores recomendam usando uma solução diferente. Gerenciador de biblioteca
(LibMan de forma abreviada) é a ferramenta de aquisição de biblioteca do lado do cliente novo do Visual Studio (Visual Studio
15,8 ou posterior). Para obter mais informações, consulte Aquisição de biblioteca do lado do cliente no ASP.NET Core com
LibMan. Bower tem suporte no Visual Studio versão 15.5.
Yarn com Webpack é uma alternativa popular para a qual instruções de migração estão disponíveis.
Bower chama a próprio "Um Gerenciador de pacotes para a web". Dentro do ecossistema do .NET, ele preenche
essa lacuna deixado pela incapacidade do NuGet para entregar os arquivos de conteúdo estático. Para projetos do
ASP.NET Core, esses arquivos estáticos são inerentes às bibliotecas do lado do cliente, como jQuery e Bootstrap.
Para bibliotecas do .NET, você usar NuGet Gerenciador de pacotes.
Processo de compilação de novos projetos criados com os modelos de projeto do ASP.NET Core configurar lado
do cliente. jQuery e Bootstrap estiverem instalados, e Bower é suportado.
Pacotes do lado do cliente são listados na bower. JSON arquivo. Os modelos de projeto do ASP.NET Core
configura bower. JSON com o jQuery, validação do jQuery e Bootstrap.
Neste tutorial, vamos adicionar suporte para Font Awesome. Pacotes do bower podem ser instalados com o
gerenciar pacotes do Bower interface do usuário ou manualmente na bower. JSON arquivo.
Instalação por meio de pacotes do Bower gerenciar 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 do Bower
(como alternativa, no menu principal, Project > gerenciar pacotes Bower).
No Bower: <nome do projeto> janela, clique na guia "Procurar" e, em seguida, filtre a lista de pacotes
inserindo font-awesome na caixa de pesquisa:
Confirme se o "Salvar alterações bower. JSON" caixa de seleção está 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 no bower. JSON
Abra o bower. JSON arquivo e adicione "font-incrível" para as dependências. O 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 corresponder ao que você vê.
Usos para bower 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 à versão principal mais recente e o til () corresponde a versão
secundária mais recente.
Salvar a bower. JSON arquivo. Visual Studio inspeciona as bower. JSON arquivo para que as alterações. Ao salvar,
o bower install comando é executado. Consulte a janela de saída npm/Bower modo de exibição para o comando
executado.
Abra o . bowerrc do arquivo sob bower. JSON. O directory estiver definida como wwwroot/lib que indica o local
do Bower instalará os ativos do pacote.
{
"directory": "wwwroot/lib"
}
Você pode usar a caixa de pesquisa no Gerenciador de soluções para localizar e exibir o pacote font awesome.
Abra o Views\Shared_layout. cshtml arquivo e adicione o arquivo CSS font awesome no ambiente auxiliar de
marca para Development . No Gerenciador de soluções, arraste e solte 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é a exibição About sobre para verificar se o pacote font awesome funciona.
{
"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. Ver arquivos estáticos para obter mais informações.
Substitua o conteúdo do index. HTML com a seguinte marcação:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Bower Example</title>
<link href="lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
</head>
<body>
<div class="jumbotron">
<h1>Using the jumbotron style</h1>
<p>
<a class="btn btn-primary btn-lg" role="button">Stateful button</a>
</p>
</div>
<script src="lib/jquery/dist/jquery.js"></script>
<script src="lib/bootstrap/dist/js/bootstrap.js"></script>
<script>
$(".btn").click(function () {
$(this).text('loading')
.delay(1000)
.queue(function () {
$(this).text('reset');
$(this).dequeue();
});
});
</script>
</body>
</html>
Execute o aplicativo e navegue até http://localhost:<port>/Index.html . Como alternativa, com index. HTML
aberto, pressione Ctrl+Shift+W . Verifique se que o estilo de jumbotron é aplicado, o código jQuery
responde quando o botão é clicado e que o Bootstrap botão muda de estado.
Less, Sass e fonte Awesome no ASP.NET Core
22/06/2018 • 22 minutes 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.
Agrupar e minificar ativos estáticos no ASP.NET Core
22/11/2018 • 18 minutes to read • Edit Online
Além de remover os comentários e espaço em branco desnecessário, os seguintes nomes de parâmetro e variável
foram renomeados da seguinte maneira:
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, este exemplo foi executado localmente. Maior ganhos de desempenho são obtidos ao
usar o agrupamento e minificação com ativos transferidos por uma rede.
O bundleconfig.json arquivo define as opções para cada pacote. No exemplo anterior, uma configuração de pacote
único é definida para o JavaScript personalizado (wwwroot/js/site.js) e a folha de estilos (wwwroot/css/site.css)
arquivos.
Opções de configuração incluem:
outputFileName : O nome do arquivo de pacote de saída. Pode conter um caminho relativo do
bundleconfig.json arquivo. Necessário
inputFiles : Uma matriz de arquivos para agrupar. Esses são os caminhos relativos para o arquivo de
configuração. opcional, * um valor vazio resulta em um arquivo de saída vazia. recurso de curinga padrões
são suportados.
minify : As opções de minimização para o tipo de saída. opcional, padrão: minify: { enabled: true }
Opções de configuração estão disponíveis por tipo de arquivo de saída.
Minificador CSS
Minificador de JavaScript
Minificador de HTML
includeInProject : O sinalizador que indica se deseja adicionar os arquivos gerados para o arquivo de projeto.
opcional, padrão – false
sourceMap : O sinalizador que indica se é necessário gerar um mapa de código-fonte para o arquivo agrupado.
opcional, padrão – false
sourceMapRootPath : O caminho raiz para armazenar o arquivo de mapa de código-fonte gerado.
NOTE
BuildBundlerMinifier pertence a um projeto voltado à comunidade no GitHub para o qual a Microsoft fornece sem suporte.
Problemas devem ser arquivados aqui.
Visual Studio
CLI do .NET Core
Adicione a BuildBundlerMinifier pacote ao seu projeto.
Compile o projeto. O exemplo a seguir é exibida 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 ==========
NOTE
BundlerMinifier.Core pertence a um projeto voltado à comunidade no GitHub para o qual a Microsoft fornece sem suporte.
Problemas devem ser arquivados aqui.
Este pacote estende a CLI do .NET Core para incluir a pacote dotnet 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
IMPORTANT
Gerenciador de pacotes NuGet adiciona as dependências para o arquivo *. csproj como <PackageReference /> nós. O
dotnet bundle comando está registrado com a CLI do .NET Core somente quando um <DotNetCliToolReference /> nó
é usado. Modifique o arquivo *. csproj adequadamente.
footer {
margin-top: 10px;
}
Para minificar Custom. CSS e agrupá-la com CSS em um site 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
}
]
NOTE
Como alternativa, é possível usar o seguinte padrão de recurso de curinga:
"inputFiles": ["wwwroot/**/*(*.css|!(*.min.css))"]
Esse padrão de recurso de curinga corresponde a todos os arquivos CSS e exclui o padrão de arquivo reduzido.
Compile o aplicativo. Abra site e observe o conteúdo da Custom. CSS é acrescentado ao final do arquivo.
<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
O seguinte environment marca renderiza os arquivos CSS agrupados e minificados quando em execução em um
ambiente diferente de Development . Por exemplo, em execução no Production ou Staging dispara o
processamento dessas folhas de estilo:
<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>
<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>
NOTE
A extensão Bundler & Minifier pertence a um projeto voltado à comunidade no GitHub para o qual a Microsoft fornece sem
suporte. Problemas devem ser arquivados aqui.
Clique com botão direito do bundleconfig.json arquivo no Gerenciador de soluções e selecione Bundler &
Minifier > converter Gulp... :
O gulpfile. js e Package. JSON arquivos são adicionados ao projeto. O suporte a npm os pacotes listados na
Package. JSON do arquivo devDependencies seção estão instalados.
Execute o seguinte comando na janela do PMC para instalar a CLI do Gulp como uma dependência global:
npm i -g gulp-cli
'use strict';
Converter manualmente
Se o Visual Studio e/ou a extensão Bundler & Minifier não estiverem disponível, converta manualmente.
Adicionar um Package. JSON arquivo pelo seguinte devDependencies , para a raiz do projeto:
"devDependencies": {
"del": "^3.0.0",
"gulp": "^4.0.0",
"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 comando a seguir no mesmo nível que Package. JSON:
npm i
npm i -g gulp-cli
const regex = {
css: /\.css$/,
html: /\.(html|htm)$/,
js: /\.js$/
};
gulp.task('min:js',
() => merge(getBundles(regex.js).map(bundle => {
return gulp.src(bundle.inputFiles, { base: '.' })
.pipe(concat(bundle.outputFileName))
.pipe(uglify())
.pipe(gulp.dest('.'));
})));
gulp.task('min:css',
() => merge(getBundles(regex.css).map(bundle => {
return gulp.src(bundle.inputFiles, { base: '.' })
.pipe(concat(bundle.outputFileName))
.pipe(cssmin())
.pipe(gulp.dest('.'));
})));
gulp.task('min:html',
() => merge(getBundles(regex.html).map(bundle => {
return gulp.src(bundle.inputFiles, { base: '.' })
.pipe(concat(bundle.outputFileName))
.pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))
.pipe(gulp.dest('.'));
})));
gulp.task('clean', () => {
return del(bundleconfig.map(bundle => bundle.outputFileName));
});
gulp.task('watch', () => {
getBundles(regex.js).forEach(
bundle => gulp.watch(bundle.inputFiles, gulp.series(["min:js"])));
getBundles(regex.css).forEach(
bundle => gulp.watch(bundle.inputFiles, gulp.series(["min:css"])));
getBundles(regex.html).forEach(
bundle => gulp.watch(bundle.inputFiles, gulp.series(['min:html'])));
});
gulp.task('default', gulp.series(['min']));
Executar tarefas de Gulp
Para disparar a tarefa do Gulp minificação antes que o projeto é compilado no Visual Studio, adicione o seguinte
destino do MSBuild para o arquivo *. csproj:
Neste exemplo, as tarefas definidas dentro de MyPreCompileTarget execução antes do predefinidos de destino
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 Gerenciador de executor de tarefas do Visual Studio pode ser usado para associar as tarefas
de Gulp a eventos específicos do Visual Studio. Ver execução de tarefas padrão para obter instruções sobre como
fazer isso.
Recursos adicionais
Usar o Gulp
Usar o Grunt
Usar vários ambientes
Auxiliares de marcação
Link do navegador no ASP.NET Core
23/08/2018 • 8 minutes to read • Edit Online
Como esse é um recurso do Visual Studio, a maneira mais fácil para adicionar o pacote para um vazio ou API da
Web projeto modelo é abrir o Package Manager Console (Modo de exibição > Other Windows > Package
Manager Console) e execute o seguinte comando:
install-package Microsoft.VisualStudio.Web.BrowserLink
Como alternativa, você pode usar Gerenciador de pacotes NuGet. Clique com botão direito no nome do projeto
no Gerenciador de soluções e escolha Manage NuGet Packages:
Localizar e instalar o pacote:
Configuração
No método Startup.Configure :
app.UseBrowserLink();
Normalmente, o código está dentro de um if bloco que apenas permite que o Link do navegador no ambiente de
desenvolvimento, como mostrado aqui:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
NOTE
Alguns plug-ins do Visual Studio, mais notavelmente Web extensão Pack 2015 e 2017 de pacote de extensão do Web,
oferecem funcionalidade estendida para o Link do navegador, mas alguns dos recursos adicionais não funcionam com o ASP.
Projetos do .NET Core.
Para abrir vários navegadores, ao mesmo tempo, 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 de índice aberto e dois
navegadores abertas:
Passe o mouse sobre o controle de barra de ferramentas do Link do navegador para ver os navegadores que estão
conectados ao projeto:
Altere o modo de exibição de í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ê inicie 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 mostra cada
navegador:
Se desejar, você pode clicar em um nome de navegador listados para atualizar esse único navegador.
Habilitar ou desabilitar o Link do navegador
Quando você habilita 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, os navegadores conectados serão atualizados
automaticamente quando você fizer qualquer alteração em arquivos CSS.
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 navegador é todo JavaScript, ele funciona em todos os navegadores SignalR dá suporte a sem
a necessidade de um plug-in de navegador.
Introdução aos Componentes Razor
04/02/2019 • 13 minutes to read • Edit Online
NOTE
O Razor Components do ASP.NET Core é compatível com ASP.NET Core 3.0 ou posterior.
Blazor é uma estrutura da Web sem suporte e experimental que não deve ser usada para cargas de trabalho de produção no
momento.
Os Componentes Razor do ASP.NET Core executam o lado do servidor no ASP.NET Core, enquanto o Blazor
(Componentes Razor que executam o lado do cliente) é uma estrutura da Web experimental do .NET que usa
C#/Razor e HTML executados no navegador com o WebAssembly. O Blazor fornece todos os benefícios de uma
estrutura de interface do usuário da Web do lado do cliente usando o .NET no cliente.
O desenvolvimento para a Web foi aprimorado de diversas maneiras ao longo dos anos, mas a criação de
aplicativos Web modernos ainda apresenta desafios. O uso do .NET no navegador oferece muitas vantagens que
podem ajudar a tornar o desenvolvimento para Web mais fácil e mais produtivo:
Estabilidade e consistência: o .NET fornece estruturas de programação padronizadas entre plataformas, que
são estáveis, completas e fáceis de usar.
Linguagens modernas inovadoras: as linguagens do .NET estão melhorando constantemente com novos
recursos de linguagem inovadores.
Ferramentas líderes do setor: a família de produtos do Visual Studio fornece uma experiência fantástica de
desenvolvimento no .NET entre plataformas, em Windows, Linux e macOS.
Velocidade e escalabilidade: o .NET tem um forte histórico de desempenho, confiabilidade e segurança para
o desenvolvimento de aplicativos. O uso do .NET como uma solução de pilha completa facilita a criação de
aplicativos rápidos, confiáveis e seguros.
Desenvolvimento de pilha completa que aproveita as habilidades existentes: os desenvolvedores
C#/Razor usam as habilidades em C#/Razor que eles já têm para escrever o código do lado do cliente e
compartilhar a lógica do lado do servidor e do lado do cliente entre os aplicativos.
Amplo suporte a navegadores: os Componentes Razor renderizam a interface do usuário como uma
marcação e um JavaScript comuns. O Blazor é executado no .NET, no navegador, usando padrões abertos da
Web sem nenhum plug-in e nenhuma transpilação de código. O Blazor funciona em todos os navegadores da
Web modernos, incluindo os navegadores móveis.
Modelos de hospedagem
Modelo de hospedagem do lado do servidor
Como os Componentes Razor desvinculam a lógica de renderização de um componente da maneira de aplicar as
atualizações da interface do usuário, há flexibilidade na maneira de hospedar os Componentes Razor. Os
Componentes Razor do ASP.NET Core no .NET Core 3.0 adicionam suporte para a hospedagem dos
Componentes Razor no servidor, em um aplicativo ASP.NET Core, em que todas as atualizações da interface do
usuário são realizadas por uma conexão SignalR. O tempo de execução realiza o envio de eventos da interface do
usuário do navegador para o servidor e, depois de executar os componentes, aplica as atualizações na interface do
usuário retornadas pelo servidor ao navegador. A mesma conexão também é usada para realizar as chamadas de
interoperabilidade do JavaScript.
Para obter mais informações, consulte Razor Components hosting models.
Modelo de hospedagem do lado do cliente
A execução do código do .NET em navegadores da Web é possibilitada por uma tecnologia relativamente nova, o
WebAssembly (abreviado como wasm). O WebAssembly é um padrão aberto da Web compatível com
navegadores da Web sem plug-ins. O WebAssembly é um formato de código de bytes compacto, otimizado para
download rápido e máxima velocidade de execução.
O código do WebAssembly pode acessar a funcionalidade completa do navegador por meio da interoperabilidade
do JavaScript. Ao mesmo tempo, o código do WebAssembly é executado na mesma área restrita confiável que o
JavaScript para impedir ações mal-intencionadas no computador cliente.
Componentes
Os aplicativos são criados com componentes. Um componente é uma parte da interface do usuário, como uma
página, uma caixa de diálogo ou um formulário de entrada de dados. Os componentes podem ser aninhados,
reutilizados e compartilhados entre os projetos.
Um componente é uma classe do .NET. A classe também pode ser escrita diretamente, como uma classe C# (*.cs)
ou, com mais frequência, na forma de uma página de marcação Razor (*.cshtml).
O Razor é uma sintaxe para a combinação da marcação HTML com o código C#. O Razor foi projetado visando a
produtividade do desenvolvedor, permitindo que ele mude entre a marcação e o C# no mesmo arquivo com o
suporte do IntelliSense. A marcação a seguir é um exemplo de um componente básico de caixa de diálogo
personalizada em um arquivo Razor (DialogComponent.cshtml):
<div>
<h2>@Title</h2>
@BodyContent
<button onclick=@OnOK>OK</button>
</div>
@functions {
public string Title { get; set; }
public RenderFragment BodyContent { get; set; }
public Action OnOK { get; set; }
}
Quando esse componente é usado em outro lugar no aplicativo, o IntelliSense acelera o desenvolvimento com o
preenchimento de sintaxe e de parâmetro.
Os componentes podem:
ser aninhados.
ser criados com o Razor (*.cshtml) ou com o código C# (*.cs).
ser compartilhados por meio de bibliotecas de classes.
receber um teste de unidade sem precisar de um navegador DOM.
Infraestrutura
Os Componentes Razor oferecem os principais recursos que a maioria dos aplicativos exige, como:
Layouts
Roteamento
Injeção de dependência
Todos esses recursos são opcionais. Quando um desses recursos não é usado em um aplicativo, a implementação
é eliminada do aplicativo quando ele é publicado pelo Vinculador de IL (linguagem intermediária).
Interoperabilidade do JavaScript
Para aplicativos que exigem bibliotecas JavaScript e APIs do navegador de terceiros, o WebAssembly foi projetado
para interoperar com o JavaScript. Os Componentes Razor são capazes de usar qualquer biblioteca ou API que o
JavaScript possa usar. O código C# pode chamar o código JavaScript, e o código JavaScript pode chamar o código
C#. Para obter mais informações, confira Interoperabilidade do JavaScript.
Otimização
Para aplicativos do lado do cliente, o tamanho do conteúdo é crítico. O Blazor otimiza o tamanho do conteúdo para
reduzir os tempos de download. Por exemplo, as partes não usadas dos assemblies do .NET são removidas
durante o processo de build, as respostas HTTP são compactadas e o tempo de execução do .NET e os assemblies
são armazenados em cache no navegador.
Os Componentes Razor fornecem um tamanho de conteúdo ainda menor do que o Blazor mantendo os
assemblies do .NET, o assembly do aplicativo e o lado do servidor do tempo de execução. Os aplicativos dos
Componentes Razor fornecem somente marcação, scripts e folhas de estilos aos clientes.
Implantação
Use o Blazor para criar um aplicativo do lado do cliente autônomo puro ou um aplicativo do ASP.NET Core de
pilha completa que contenha os aplicativos cliente e os aplicativos para servidor:
Em um aplicativo autônomo do lado do cliente, o aplicativo Blazor é compilado em uma pasta dist que
contém apenas arquivos estáticos. Os arquivos podem ser hospedados no Serviço de Aplicativo do Azure, nas
páginas do GitHub, no IIS (configurado como um servidor de arquivos estático), nos servidores do Node.js e
em vários outros servidores e serviços. O .NET não é necessário no servidor de produção.
Em um aplicativo do ASP.NET Core de pilha completa, o código pode ser compartilhado entre os
aplicativos cliente e os aplicativos para servidor. O aplicativo dos Componentes Razor do ASP.NET Core
resultante, que atende à interface do usuário do lado do cliente e a outros pontos de extremidade de API do
lado do servidor, pode ser criado e implantado em qualquer host de nuvem ou local compatível com o
ASP.NET Core.
Recursos adicionais
WebAssembly
Guia do C#
Razor
HTML
Hospedar e implantar Componentes Razor
05/02/2019 • 25 minutes to read • Edit Online
NOTE
O Razor Components do ASP.NET Core é compatível com ASP.NET Core 3.0 ou posterior.
Blazor é uma estrutura da Web sem suporte e experimental que não deve ser usada para cargas de trabalho de produção no
momento.
Publique o aplicativo
Os aplicativos são publicados para implantação na configuração de versão com o comando dotnet publish. Um IDE
pode manipular a execução do comando dotnet publish automaticamente usando recursos de publicação
internos, para que não seja necessário executar o comando manualmente usando um prompt de comando,
dependendo das ferramentas de desenvolvimento em uso.
O dotnet publish dispara uma restauração das dependências do projeto e compila o projeto antes de criar os
ativos para implantação. Como parte do processo de build, os assemblies e métodos não usados são removidos
para reduzir o tamanho de download do aplicativo e os tempos de carregamento. A implantação é criada na pasta
/bin/Release/<target-framework>/publish.
Os ativos na pasta publish são implantados no servidor Web. A implantação pode ser um processo manual ou
automatizado, dependendo das ferramentas de desenvolvimento em uso.
Adicione uma entrada ao arquivo launchSettings.json do aplicativo no perfil do IIS Express. Essa
configuração é obtida ao executar o aplicativo com o Depurador do Visual Studio e ao executar o aplicativo
em um prompt de comando com dotnet run .
"commandLineArgs": "--contentroot=/<content-root>"
No Visual Studio, especifique o argumento em Propriedades > Depurar > Argumentos de aplicativo. A
configuração do argumento na página de propriedades do Visual Studio adiciona o argumento ao arquivo
launchSettings.json.
--contentroot=/<content-root>
Caminho base
O argumento --pathbase define o caminho base do aplicativo para um aplicativo executado localmente com um
caminho virtual não raiz (a tag href <base> é definida como um caminho diferente de / para preparo e
produção). Para obter mais informações, confira a seção Caminho base do aplicativo.
IMPORTANT
Ao contrário do caminho fornecido ao href da tag <base> , não inclua uma barra à direita ( / ) ao passar o valor do
argumento --pathbase . Se o caminho base do aplicativo for fornecido na tag <base> como <base href="/CoolApp/" />
(inclui uma barra à direita), passe o valor do argumento de linha de comando como --pathbase=/CoolApp (nenhuma barra
à direita).
Adicione uma entrada ao arquivo launchSettings.json do aplicativo no perfil do IIS Express. Essa
configuração é obtida ao executar o aplicativo com o Depurador do Visual Studio e ao executar o aplicativo
em um prompt de comando com dotnet run .
"commandLineArgs": "--pathbase=/<virtual-path>"
No Visual Studio, especifique o argumento em Propriedades > Depurar > Argumentos de aplicativo. A
configuração do argumento na página de propriedades do Visual Studio adiciona o argumento ao arquivo
launchSettings.json.
--pathbase=/<virtual-path>
URLs
O argumento --urls indica os endereços IP ou os endereços de host com portas e protocolos para escutar
solicitações.
Passe o argumento ao executar o aplicativo localmente em um prompt de comando. No diretório do
aplicativo, execute:
Adicione uma entrada ao arquivo launchSettings.json do aplicativo no perfil do IIS Express. Essa
configuração é obtida ao executar o aplicativo com o Depurador do Visual Studio e ao executar o aplicativo
em um prompt de comando com dotnet run .
"commandLineArgs": "--urls=http://127.0.0.1:0"
No Visual Studio, especifique o argumento em Propriedades > Depurar > Argumentos de aplicativo. A
configuração do argumento na página de propriedades do Visual Studio adiciona o argumento ao arquivo
launchSettings.json.
--urls=http://127.0.0.1:0
<handlers>
<remove name="aspNetCore" />
</handlers>
Como alternativa, desabilite a herança da seção <system.webServer> do aplicativo raiz (pai) usando um elemento
<location> com inheritInChildApplications definido como false :
A ação de remover o manipulador ou desabilitar a herança é realizada além da ação da configurar o caminho base do
aplicativo, conforme descrito nesta seção. Defina o caminho base do aplicativo no arquivo index.html do aplicativo do alias do
IIS usado ao configurar o subaplicativo no IIS.
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /Index.html =404;
}
}
}
Para obter mais informações sobre a configuração do servidor Web Nginx de produção, confira Creating NGINX
Plus and NGINX Configuration Files (Criando arquivos de configuração do NGINX Plus e do NGINX).
Hospedagem autônoma do Blazor do lado do cliente no Docker
Para hospedar o Blazor no Docker usando o Nginx, configure o Dockerfile para usar a imagem do Nginx baseada
no Alpine. Atualize o Dockerfile para copiar o arquivo nginx.config no contêiner.
Adicione uma linha ao Dockerfile, conforme é mostrado no exemplo a seguir:
FROM nginx:alpine
COPY ./bin/Release/netstandard2.0/publish /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/nginx.conf
NOTE
O Razor Components do ASP.NET Core é compatível com ASP.NET Core 3.0 ou posterior.
Blazor é uma estrutura da Web sem suporte e experimental que não deve ser usada para cargas de trabalho de produção no
momento.
O Blazor executa a vinculação de IL (linguagem intermediária) durante cada build do Modo de Versão para
remover IL desnecessária dos assemblies de saída.
Você pode controlar a vinculação do assembly com uma das seguintes abordagens:
Desabilite a vinculação globalmente com uma propriedade MSBuild.
Controle a vinculação por assembly usando um arquivo de configuração.
<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>
Para saber mais sobre o formato de arquivo para o arquivo de configuração, veja Vinculador de IL: sintaxe do
descritor de xml.
Especifique o arquivo de configuração no arquivo de projeto com o item BlazorLinkerDescriptor :
<ItemGroup>
<BlazorLinkerDescriptor Include="Linker.xml" />
</ItemGroup>
Usar o modelo de projeto Angular com o ASP.NET
Core
15/10/2018 • 20 minutes to read • Edit Online
NOTE
Esta documentação não é sobre o modelo de projeto do Angular incluído no ASP.NET Core 2.0. Ela é sobre o modelo
Angular mais recente, para o qual você pode atualizar manualmente. O modelo está incluído no ASP.NET Core 2.1 por
padrão.
O modelo de projeto do Angular atualizado fornece um ponto inicial conveniente para aplicativos do ASP.NET
Core usando o Angular e a CLI do Angular para implementar uma IU (interface do usuário) avançada do lado do
cliente.
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 do 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.
Executar comandos ng
Em um prompt de comando, mude para o subdiretório ClientApp:
cd ClientApp
Se você tiver a ferramenta ng instalada globalmente, você poderá executar qualquer um dos seus comandos. Por
exemplo, você poderá executar ng lint , ng test ou qualquer um dos outros comandos da CLI do Angular. No
entanto, não é necessário executar ng serve , porque o seu aplicativo ASP.NET Core dá conta de servir tanto a
parte do lado do servidor quanto a do lado do cliente do seu aplicativo. Internamente, ele usa ng serve em
desenvolvimento.
Se você não tiver a ferramenta ng instalada, execute npm run ng em vez dela. 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 de um modo otimizado para conveniência do desenvolvedor. Por
exemplo, pacotes JavaScript incluem mapas de origem (de modo que durante a depuração, você pode ver o
código TypeScript original). O aplicativo observa alterações em arquivos TypeScript, HTML e CSS no disco e
recompila e recarrega automaticamente quando as detecta.
Em produção, atende a uma versão de seu aplicativo que é otimizada para desempenho. Isso é configurado para
ocorrer automaticamente. Quando você publica, a configuração de build emite um build minificado em
compilação AoT (Ahead Of Time) do código do lado do cliente. Diferentemente do build de desenvolvimento, o
build de produção não requer que o Node.js esteja instalado no servidor (a menos que você tenha habilitado a
pré-renderização do lado do servidor ).
Você pode usar os métodos padrão de implantação e hospedagem do ASP.NET Core.
cd ClientApp
npm start
IMPORTANT
Use npm start para iniciar o Development Server da CLI do Angular, não ng serve , de modo que a configuração
no package.json seja respeitada. Para passar parâmetros adicionais para o servidor da CLI do Angular, adicione-os à
linha scripts relevante em seu arquivo package.json.
2. Modifique o aplicativo ASP.NET Core para, em vez de iniciar uma instância da CLI do Angular própria, usar
a externa. Na classe Startup, substitua a invocação spa.UseAngularCliServer pelo seguinte:
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
Quando você iniciar seu aplicativo ASP.NET Core, ele não inicializará um servidor da CLI do Angular. Em vez
disso, a instância que você iniciou manualmente é usada. Isso permite a ele iniciar e reiniciar mais rapidamente. Ele
não está mais aguardando a CLI do Angular recompilar o aplicativo cliente a cada vez.
TIP
Habilitar a SSR (renderização do lado do servidor) apresenta uma série de complicações adicionais, durante o
desenvolvimento e a implantação. Leia desvantagens da SSR para determinar se a SSR é uma boa opção para as suas
necessidades.
Para habilitar a SSR, você precisa fazer um número de adições ao seu projeto.
Na classe Startup, após a linha que configura spa.Options.SourcePath e antes da 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 do Angular chamado ssr , que ainda não está
definido.
No final da matriz apps em ClientApp/.angular-cli.json, defina 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 habilitada para SSR requer dois arquivos adicionais: tsconfig.server.json e
main.server.ts. O arquivo tsconfig.server.json especifica opções de compilação de TypeScript. O arquivo
main.server.ts serve como o ponto de entrada de código durante a SSR.
Adicione um novo arquivo chamado tsconfig.server.json dentro de ClientApp/src (junto com o tsconfig.app.json
existente), contendo o seguinte:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "./",
"module": "commonjs"
},
"angularCompilerOptions": {
"entryModule": "app/app.server.module#AppServerModule"
}
}
Esse arquivo configura o compilador AoT do Angular para procurar por um módulo chamado app.server.module .
Adicione isso ao criar um novo arquivo em ClientApp/src/app/app.server.module.ts (junto com o app.module.ts
existente) que contém o seguinte:
@NgModule({
imports: [AppModule, ServerModule, ModuleMapLoaderModule],
bootstrap: [AppComponent]
})
export class AppServerModule { }
Esse módulo herda de seu app.module do lado do cliente e define quais módulos extra do Angular estão
disponíveis durante a SSR.
Lembre-se de que a nova entrada ssr em .angular cli.json referenciou de um arquivo de ponto de entrada
chamado main.server.ts. Você ainda não adicionou esse arquivo e agora é hora de fazê-lo. Crie um novo arquivo
em ClientApp/src/main.server.ts (junto com o main.ts existente), contendo 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 }
]
};
O código do arquivo é o que o ASP.NET Core executa para cada solicitação quando ele executa o middleware
UseSpaPrerendering que você adicionou à classe Startup. Ele trata do recebimento de params do código .NET (por
exemplo, a URL que está sendo solicitada) e de fazer chamadas a APIs de SSR do Angular para obter o HTML
resultante.
A rigor, isso é suficiente para habilitar a SSR no modo de desenvolvimento. É essencial para fazer uma alteração
final para que seu aplicativo funcione corretamente quando publicado. No arquivo .csproj principal do seu
aplicativo, defina o valor da propriedade BuildServerSideRenderer para true :
Isso configura o processo de build para executar build:ssr durante a publicação e implantar os arquivos SSR no
servidor. Se você não habilitar isso, a SSR falhará em produção.
Quando o aplicativo é executado no modo de desenvolvimento ou de produção, o código do Angular é
previamente renderizado como HTML no servidor. O código do lado do cliente é executado normalmente.
Passar dados de código .NET para código do TypeScript
Durante a SSR, convém passar dados por solicitação, do aplicativo ASP.NET Core para o aplicativo do Angular.
Por exemplo, você pode transmitir informações de cookie ou algo lido de um banco de dados. Para fazer isso, edite
a classe Startup. 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 retorno de chamada SupplyData permite que você passe dados serializáveis por JSON arbitrários e por
solicitação (por exemplo, cadeias de caracteres, boolianos ou números). O código de main.server.ts recebe isso
como params.data . Por exemplo, o exemplo de código anterior passa um valor booliano como
params.data.isHttpsRequest para o retorno de chamada createServerRenderer . Você pode passar isso para outras
partes do seu aplicativo de alguma forma compatível com o Angular. Por exemplo, veja como main.server.ts passa
o valor BASE_URL para qualquer componente cujo construtor é declarado para recebê-lo.
Desvantagens da SSR
Nem todos os aplicativos se beneficiam da SSR. O principal benefício é o desempenho percebido. Os visitantes
que chegam ao aplicativo por uma conexão de rede lenta ou em dispositivos móveis lentos veem a interface do
usuário inicial rapidamente, mesmo que leve algum tempo para buscar ou para analisar os pacotes de JavaScript.
No entanto, muitos SPAs são usados principalmente em redes de empresa internas e rápidas, em computadores
rápidos nos quais o aplicativo aparece quase instantaneamente.
Ao mesmo tempo, há desvantagens significativas em habilitar a SSR. Ele adiciona complexidade ao seu processo
de desenvolvimento. O código deve ser executado em dois ambientes diferentes: no lado do cliente e no do
servidor (em um ambiente Node.js invocado do ASP.NET Core). Estes são alguns pontos a considerar:
A 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 Aplicativos do Azure, mas não para outros como o Azure
Service Fabric.
Habilitar o sinalizador de build BuildServerSideRenderer faz com que o diretório node_modules seja
publicado. Esta pasta contém mais de 20.000 arquivos, o que aumenta o tempo de implantação.
Para executar o código em um ambiente Node.js, ele não pode depender da existência de APIs de
JavaScript específicas a um navegador, tais como window ou localStorage . Se seu código (ou alguma
biblioteca de terceiros à qual você faz referência) tentar usar essas APIs, você obterá um erro durante a
SSR. Por exemplo, não use jQuery porque faz referência a APIs específicas a um navegador em vários
locais. Para evitar erros, você deve evitar a SSR ou então evitar APIs ou bibliotecas específicas a um
navegador. Você pode encapsular todas as chamadas para essas APIs em verificações para garantir que elas
não sejam invocadas durante a SSR. Por exemplo, use uma verificação como a seguinte no código
JavaScript ou TypeScript:
NOTE
Esta documentação não é sobre o modelo de projeto do React incluído no ASP.NET Core 2.0. Ela é sobre o modelo React
mais recente, para o qual você pode atualizar manualmente. O modelo está incluído no ASP.NET Core 2.1 por padrão.
O modelo de projeto do React atualizado fornece um ponto inicial conveniente para aplicativos do ASP.NET Core
usando convenções do React e de CRA (criar-aplicativo-do-React) para implementar uma IU (interface do usuário)
avançada do lado do cliente.
O modelo é equivalente à criação de dois projetos: um projeto do ASP.NET Core, para atuar como um back-end
de API, e um projeto do React CRA padrão, para atuar como uma interface do usuário, mas com a praticidade de
hospedar ambos em um único projeto de aplicativo 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 de um modo otimizado para conveniência do desenvolvedor. Por
exemplo, pacotes JavaScript incluem mapas de origem (de modo que durante a depuração, você pode ver o
código-fonte original). O aplicativo observa alterações em arquivos JavaScript, HTML e CSS no disco e recompila
e recarrega automaticamente quando as detecta.
Em produção, atende a uma versão de seu aplicativo que é otimizada para desempenho. Isso é configurado para
ocorrer automaticamente. Quando você publica, a configuração de build emite um build minificado e transpilado
do seu código do lado do cliente. Diferentemente do build de desenvolvimento, o build de produção não requer
que o Node.js esteja instalado no servidor.
Você pode usar os métodos padrão de implantação e hospedagem do ASP.NET Core.
BROWSER=none
Isso impedirá o navegador da web seja aberto ao iniciar o servidor CRA externamente.
2. Em um prompt de comando, vá para o subdiretório ClientApp e inicie o Development Server do CRA:
cd ClientApp
npm start
3. Modifique o aplicativo ASP.NET Core para, em vez de iniciar uma instância do servidor CRA própria, usar
a externa. Na classe Startup, substitua a invocação spa.UseReactDevelopmentServer pelo seguinte:
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
Quando você iniciar seu aplicativo ASP.NET Core, ele não inicializará um servidor CRA. Em vez disso, a instância
que você iniciou manualmente é usada. Isso permite a ele iniciar e reiniciar mais rapidamente. Ele não aguarda
mais que o aplicativo do React seja recompilado a cada vez.
Usar o modelo de projeto do React com Redux com
o ASP.NET Core
16/01/2019 • 2 minutes to read • Edit Online
NOTE
Esta documentação não pertencem ao modelo de projeto React com Redux incluído no ASP.NET Core 2.0. Ele se refere ao
modelo de reagir com Redux mais recente que você pode atualizar manualmente. O modelo está disponível no ASP.NET Core
2.1 ou posterior.
O modelo de projeto do React com Redux atualizado fornece um ponto inicial conveniente para aplicativos do
ASP.NET Core usando convenções do React, do Redux e de CRA (create-react-app) para implementar uma IU
(interface do usuário) avançada do lado do cliente.
Com exceção do comando de criação de projeto, todas as informações sobre o modelo React com Redux são as
mesmas que aquelas sobre o modelo React. 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, confira a Documentação do modelo React.
Para obter informações sobre como configurar um aplicativo de subpropriedades React com Redux no IIS, consulte
ReactRedux modelo 2.1: Não é possível usar o SPA no IIS (aspnet/modelagem #555).
Usar JavaScriptServices para criar aplicativos de
única página no ASP.NET Core
09/12/2018 • 21 minutes to read • Edit Online
O que é JavaScriptServices
JavaScriptServices é uma coleção de tecnologias do lado do cliente para o ASP.NET Core. Sua meta é posicionar o
ASP.NET Core como plataforma de 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
Usar uma estrutura de SPA ou biblioteca
Criar ativos do lado do cliente com o Webpack
O foco deste artigo é colocado sobre como usar o pacote SpaServices.
O que é SpaServices
SpaServices foi criado para posicionar o ASP.NET Core como plataforma de servidor preferencial dos
desenvolvedores para a criação de SPAs. SpaServices não é necessário para desenvolver os SPAs com o ASP.NET
Core, e ele não bloqueie você em uma estrutura de cliente específico.
SpaServices fornece infraestrutura úteis, como:
Pré-processamento do lado do servidor
Middleware de desenvolvimento webpack
Substituição do módulo quente
Auxiliares de roteamentos
Coletivamente, esses componentes de infraestrutura aprimoram 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 precisará fazer nada aqui — Node. js está
instalado e disponível em ambientes de servidor.
SDK 2.0 ou posterior do .NET Core
Se você estiver no Windows usando o Visual Studio 2017, o SDK está instalado, selecionando o
desenvolvimento de plataforma cruzada do .NET Core carga de trabalho.
Microsoft.AspNetCore.SpaServices pacote do NuGet
npm i -S aspnet-prerendering
Configuração
Os auxiliares de marca são feitos podem ser descobertos por meio do registro do namespace do projeto
viewimports. cshtml arquivo:
@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"
Esses auxiliares de marcação abstraem as complexidades de se comunicar diretamente com as APIs de baixo nível,
utilizando uma sintaxe semelhante ao HTML dentro a exibição do 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>
Observação: Os nomes de propriedade passados na auxiliares de marca são representados com PascalCase
notação. Compare isso com 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 passados do servidor para o modo de exibição por
hydrating a globals propriedade fornecida para o resolve função:
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
O postList matriz definida dentro de globals objeto está anexado para o navegador global window objeto. Essa
elevação de variáveis em escopo global elimina a duplicação de esforço, particularmente, pois 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
Webpack Dev Middleware é registrado no pipeline de solicitação HTTP por meio de código a seguir na Startup.cs
do arquivo Configure método:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
O UseWebpackDevMiddleware método de extensão deve ser chamado antes Registrando a hospedagem de arquivos
estáticos por meio de UseStaticFiles método de extensão. Por motivos de segurança, registre o middleware
somente quando o aplicativo é executado no modo de desenvolvimento.
O webpack.config.js do arquivo output.publicPath propriedade informa o middleware para observar o dist
pasta para que as alterações:
npm i -D webpack-hot-middleware
Configuração
O componente HMR deve ser registrado no pipeline de solicitação HTTP do MVC no Configure método:
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});
Como ocorria com Webpack Dev Middleware, o UseWebpackDevMiddleware método de extensão deve ser chamado
antes do UseStaticFiles método de extensão. Por motivos de segurança, registre o middleware somente quando
o aplicativo é executado no modo de desenvolvimento.
O webpack.config.js arquivo deve definir um plugins de matriz, mesmo se ela for deixada em branco:
Auxiliares de roteamentos
A maioria dos SPAs com base no ASP.NET Core, você desejará roteamento do lado do cliente além do
roteamento do lado do servidor. Os sistemas de roteamento do SPA e o MVC podem trabalhar de forma
independente, sem interferência. Há, no entanto, os desafios de uma borda caso apresentando: identificando as
respostas HTTP 404.
Considere o cenário em que 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 seu padrão corresponde a uma rota do lado do
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 corresponder a qualquer rota do
lado do servidor ou um arquivo estático, é improvável que o aplicativo do lado do cliente seria lidará com isso,
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 primeiro para correspondência de padrões.
Para criar um novo projeto usando um dos modelos do SPA, inclua o nome curto do modelo na dotnet novo
comando. O comando a seguir cria um aplicativo Angular com ASP.NET Core MVC configurado para o lado do
servidor:
dotnet run
O aplicativo é iniciado no localhost de acordo com o modo de configuração de tempo de execução. Navegando até
http://localhost:5000 no navegador exibe a página de aterrissagem.
Testar o aplicativo
Modelos SpaServices são pré-configuradas para executar testes do lado do cliente usando Karma e Jasmine.
Jasmine é uma estrutura de teste para JavaScript, de unidade popular enquanto Karma é um executor de teste
para que esses testes. Karma está configurado para funcionar com o Webpack Dev Middleware , de modo que o
desenvolvedor não é necessário parar e executar o teste sempre que forem feitas alterações. Se ele é o código em
execução no caso de teste ou caso de teste em si, o teste é 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 teste Karma, que lê as configurações definidas na karma.conf.js arquivo. Entre outras
configurações, o karma.conf.js identifica os arquivos de teste para ser executada por meio do seu files matriz:
Publicando o aplicativo
Combinando os ativos gerados do lado do cliente e os artefatos publicados do ASP.NET Core em um pacote
pronto para implantar pode ser complicado. Felizmente, SpaServices orquestra o processo de publicação inteira
com um destino MSBuild personalizado chamado RunWebpack :
Recursos adicionais
Docs angular
Hospedar e implantar o ASP.NET Core
13/12/2018 • 7 minutes to read • Edit Online
Publicar no Azure
Confira Publicar um aplicativo ASP.NET Core no Azure com o Visual Studio para obter instruções sobre como
publicar um aplicativo no Azure usando o Visual Studio. Um exemplo adicional é fornecido em Criar um
aplicativo Web ASP.NET Core no Azure.
Recursos adicionais
Hospedar o ASP.NET Core em contêineres do Docker
Solucionar problemas de projetos do ASP.NET Core
Implantar aplicativos ASP.NET Core no Serviço de
Aplicativo do Azure
02/01/2019 • 18 minutes to read • Edit Online
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 um aplicativo ASP.NET Core 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.
Implantação contínua no Azure com o Visual Studio e o GIT com o ASP.NET Core
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.
Criar seu primeiro pipeline com o Azure Pipelines
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
Plataforma
Os tempos de execução para aplicativos de 32 bits (x86) e 64 bits (x64) estão presentes no Serviço de Aplicativo
do Azure. O SDK do .NET Core disponível no Serviço de Aplicativo do Azure é de 32 bits, mas você pode
implantar aplicativos de 64 bits usando o console do Kudu ou o MSDeploy com um perfil de publicação do
Visual Studio ou um comando da CLI.
Para aplicativos com dependências nativas, os tempos de execução para aplicativos de 32 bits (x86) estão
presentes no Serviço de Aplicativo do Azure. O SDK do .NET Core disponível no Serviço de Aplicativo é de 32
bits.
Pacotes
Incluem os seguintes pacotes NuGet para fornecer recursos de registro automático em log para aplicativos
implantados no Serviço de Aplicativo do Azure:
O 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.
Os pacotes anteriores não estão disponíveis no Microsoft.AspNetCore.App metapackage. Os aplicativos que são
direcionados ao .NET Framework ou fazem referência a um metapacote Microsoft.AspNetCore.App precisam
referenciar explicitamente os pacotes individuais no arquivo de projeto do aplicativo.
Test-Path D:\home\SiteExtensions\AspNetCoreRuntime.{X.Y}.{PLATFORM}\
O comando retornará True quando o tempo de execução da versão prévia x64 estiver instalado.
NOTE
A arquitetura da plataforma (x86/x64) de um aplicativo dos Serviços de Aplicativos é definida nas configurações do
aplicativo no portal do Azure para aplicativos hospedados em um nível de hospedagem de computação da série A ou
melhor. Se o aplicativo for executado no modo em processo e a arquitetura da plataforma estiver configurada para 64 bits
(x64), o Módulo do ASP.NET Core usará o tempo de execução da versão prévia de 64 bits, se estiver presente. Instale a
extensão Tempo de execução do ASP.NET Core {X.Y} (x64).
Depois de instalar o tempo de execução da versão prévia x64, execute o seguinte comando na janela de comando do Kudu
PowerShell para verificar a instalação. Substitua a versão de tempo de execução do ASP.NET Core por {X.Y} no comando:
Test-Path D:\home\SiteExtensions\AspNetCoreRuntime.{X.Y}.x64\
O comando retornará True quando o tempo de execução da versão prévia x64 estiver instalado.
NOTE
As Extensões do ASP.NET Core habilitam uma funcionalidade adicional para o ASP.NET Core nos Serviços de Aplicativo
do Azure, como a habilitação do registro em log do Azure. A extensão é instalada automaticamente durante a implantação
do Visual Studio. Se a extensão não estiver instalada, instale-a para o aplicativo.
{
"type": "siteextensions",
"name": "AspNetCoreRuntime",
"apiVersion": "2015-04-01",
"location": "[resourceGroup().location]",
"properties": {
"version": "[parameters('aspnetcoreVersion')]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', parameters('siteName'))]"
]
}
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup>
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
Hospedar o ASP.NET Core em um web farm
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
Módulo do ASP.NET Core
Módulo do ASP.NET Core
Módulos do IIS com o ASP.NET Core
Biblioteca do Microsoft TechNet: Windows Server
Publicar um aplicativo ASP.NET Core no Azure com
o Visual Studio
10/01/2019 • 7 minutes to read • Edit Online
IMPORTANT
Versões prévias do ASP.NET Core com o Serviço de Aplicativo do Azure
Versões prévias do ASP.NET Core não são implantadas para o Serviço de Aplicativo do Azure por padrão. Para hospedar
um aplicativo que usa uma versão prévia do ASP.NET Core, veja Implantar versão prévia do ASP.NET Core para o Serviço
de Aplicativo do Azure.
Confira Publicar no Azure do Visual Studio para Mac se você estiver trabalhando no macOS.
Para solucionar um problema de implantação do Serviço de Aplicativo, confira Solucionar problemas no
ASP.NET Core no Serviço de Aplicativo do Azure.
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
do SQL: não é possível abrir o banco de dados. A aplicação de migrações existentes ao contexto do BD
do Aplicativo pode resolver esse problema."
Selecione Aplicar Migrações e, depois que a página for atualizada, atualize a página.
O aplicativo exibe o email usado para registrar o novo usuário e um link Fazer logout.
Implantar o aplicativo no Azure
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Publicar....
Na caixa de diálogo Publicar:
Selecione Serviço de Aplicativo do Microsoft Azure.
Selecione o ícone de engrenagem e, em seguida, Criar Perfil.
Selecione Criar Perfil.
Criar recursos do Azure
A caixa de diálogo Criar Serviço de Aplicativo será exibida:
Insira sua assinatura.
Os campos de entrada Nome do Aplicativo, Grupo de Recursos e Plano do Serviço de Aplicativo
serão populados. Você pode manter esses nomes ou alterá-los.
Selecione a guia Serviços para criar um novo banco de dados.
Selecione o ícone verde + para criar um novo Banco de Dados SQL
Selecione Novo... na caixa de diálogo Configurar Banco de Dados SQL para criar um novo banco de
dados.
NOTE
“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, confira Recursos adicionais.
Quando a implantação for concluída, selecione Configurações:
@page
@model AboutModel
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@Model.Message</h3>
Recursos adicionais
Serviço de Aplicativo do Azure
Grupo de recursos do Azure
Banco de Dados SQL do Azure
Perfis de publicação do Visual Studio para a implantação do aplicativo ASP.NET Core
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Implantação contínua no Azure com o Visual Studio
e o GIT com o ASP.NET Core
10/01/2019 • 13 minutes to read • Edit Online
IMPORTANT
Versões prévias do ASP.NET Core com o Serviço de Aplicativo do Azure
Versões prévias do ASP.NET Core não são implantadas para o Serviço de Aplicativo do Azure por padrão. Para hospedar um
aplicativo que usa uma versão prévia do ASP.NET Core, veja Implantar versão prévia do ASP.NET Core para o Serviço de
Aplicativo do Azure.
Este tutorial mostra como criar um aplicativo Web ASP.NET Core usando o Visual Studio e implantá-lo por meio
do Visual Studio no Serviço de Aplicativo do Azure usando a implantação contínua.
Consulte também Criar seu primeiro pipeline com o Azure Pipelines, que mostra como configurar um fluxo de
trabalho de CD (entrega contínua) para o Serviço de Aplicativo do Azure usando o Azure DevOps Services. O
Azure Pipelines (um serviço do Azure DevOps 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.
NOTE
Para concluir este tutorial, você precisa de uma conta do Microsoft Azure. Para obter uma conta, ative os benefícios do
assinante do MSDN ou inscreva-se em uma avaliação gratuita.
Pré-requisitos
Este tutorial pressupõe que o seguinte software está instalado:
Visual Studio
SDK 2.0 ou posterior do .NET Core
Git para Windows
NOTE
A versão mais recente do .NET Core é a 2.0.
2. Depois de examinar o aplicativo Web em execução, feche o navegador e selecione o ícone “Parar
Depuração” na barra de ferramentas do Visual Studio para interromper o aplicativo.
5. Selecione OK.
6. Caso você não tenha configurado anteriormente as credenciais de implantação para publicar um aplicativo
Web ou outro aplicativo do Serviço de Aplicativo, configure-as agora:
Selecione Configurações > Credenciais de implantação. A folha Definir credenciais de
implantação é exibida.
Crie um nome de usuário e uma senha. Salve a senha para uso posterior ao configurar o GIT.
Selecione Salvar.
7. Na folha Aplicativo Web, selecione Configurações > Propriedades. A URL do repositório GIT remoto
no qual você implantará será 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. Na seção Remotos das Configurações do Repositório, selecione Adicionar. A caixa de diálogo
Adicionar Remoto é exibida.
4. Defina o Nome do remoto como Azure-SampleApp.
5. Defina o valor de Buscar como a URL do GIT copiada do Azure anteriormente neste tutorial. Observe que
essa é a URL que termina com .git.
NOTE
Como alternativa, especifique o repositório remoto na Janela Comando abrindo a Janela Comando, alterando
para o diretório do projeto e inserindo o comando. Exemplo:
git remote add Azure-SampleApp https://[email protected]:443/SampleApp.git
6. Selecione a Página Inicial (ícone da página inicial) > Configurações > Configurações Globais.
Confirme se o nome e o endereço de email estão definidos. Se necessário, selecione Atualizar.
7. Selecione Página Inicial > Alterações para retornar à exibição Alterações.
8. Escreva uma mensagem de confirmação, como Push Inicial nº 1 e selecione Confirmar. Essa ação cria
uma confirmação localmente.
NOTE
Como alternativa, confirme as alterações na Janela Comando abrindo a Janela Comando, alterando para o
diretório do projeto e inserindo os comandos do GIT. Exemplo:
git add .
9. Selecione Página Inicial > Sincronização > Ações > Abrir Prompt de Comando. O prompt de
comando se abre no diretório do projeto.
10. Insira o seguinte comando na janela de comando:
git push -u Azure-SampleApp master
11. Insira a senha das credenciais de implantação do Azure criada anteriormente no Azure.
Esse comando inicia o processo de envio por push dos arquivos de projeto locais para o Azure. A saída do
comando acima termina com uma mensagem informando que a implantação foi bem-sucedida.
NOTE
Como alternativa, envie as alterações por push da Janela Comando abrindo a Janela Comando, alterando para o diretório
do projeto e inserindo um comando do GIT. Exemplo:
git push -u Azure-SampleApp master
Recursos adicionais
Criar seu primeiro pipeline com o Azure Pipelines
Kudu do projeto
Perfis de publicação do Visual Studio para a implantação do aplicativo ASP.NET Core
Módulo do ASP.NET Core
30/01/2019 • 43 minutes to read • Edit Online
Por Tom Dykstra, Rick Strahl, Chris Ross, Rick Anderson, Sourabh Shirhatti, Justin Kotalik e Luke Latham
O módulo do ASP.NET Core é um módulo nativo do IIS que se conecta ao pipeline do IIS para:
Hospedar um aplicativo ASP.NET Core dentro do processo de trabalho do IIS ( w3wp.exe ), chamado o modelo
de hospedagem no processo.
Encaminhar solicitações da Web a um aplicativo ASP.NET Core de back-end que executa o servidor Kestrel,
chamado o modelo de hospedagem de fora do processo.
Versões do Windows compatíveis:
Windows 7 ou posterior
Windows Server 2008 R2 ou posterior
Ao fazer uma hospedagem em processo, o módulo usa uma implementação de servidor em processo do IIS,
chamado Servidor HTTP do IIS ( IISHttpServer ).
Ao hospedar de fora do processo, o módulo só funciona com o Kestrel. O módulo é incompatível com HTTP.sys.
Modelos de hospedagem
Modelo de hospedagem em processo
Para configurar um aplicativo para hospedagem em processo, adicione a propriedade <AspNetCoreHostingModel> ao
arquivo de projeto do aplicativo com um valor de InProcess (a hospedagem de fora do processo é definida com
OutOfProcess ):
<PropertyGroup>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
Não há suporte para o modelo de hospedagem em processo para aplicativos ASP.NET Core direcionados ao .NET
Framework.
Se a propriedade <AspNetCoreHostingModel> não estiver presente no arquivo, o valor padrão será OutOfProcess .
As seguintes características se aplicam ao hospedar em processo:
O Servidor HTTP do IIS ( IISHttpServer ) é usado, em vez do servidor Kestrel.
O requestTimeout atributo não se aplica à hospedagem em processo.
Não há suporte para o compartilhamento do pool de aplicativos entre aplicativos. Use um pool de
aplicativos por aplicativo.
Ao usar a Implantação da Web ou inserir manualmente um arquivo app_offline.htm na implantação, o
aplicativo talvez não seja desligado imediatamente se houver uma conexão aberta. Por exemplo, uma
conexão websocket pode atrasar o desligamento do aplicativo.
A arquitetura (número de bit) do aplicativo e o tempo de execução instalado (x64 ou x86) devem
corresponder à arquitetura do pool de aplicativos.
Se a configuração manual do host do aplicativo com WebHostBuilder (não usando CreateDefaultBuilder) e o
aplicativo não forem executados diretamente no servidor Kestrel (auto-hospedado), chame UseKestrel
antes de chamar UseIISIntegration . Se a ordem for invertida, a inicialização do host falhará.
As desconexões do cliente são detectadas. O token de cancelamento HttpContext.RequestAborted é
cancelado quando o cliente se desconecta.
GetCurrentDirectory retorna o diretório de trabalho do processo iniciado pelo IIS em vez de o diretório do
aplicativo (por exemplo, C:\Windows\System32\inetsrv para w3wp.exe).
Para obter o código de exemplo que define o diretório atual do aplicativo, confira a classe
CurrentDirectoryHelpers. Chame o método SetCurrentDirectory . As chamadas seguintes a
GetCurrentDirectory fornecem o diretório do aplicativo.
Modelo de hospedagem de fora do processo
Para configurar um aplicativo para hospedagem fora do processo, use qualquer uma das abordagens a seguir no
arquivo de projeto:
não especifique a propriedade <AspNetCoreHostingModel> . Se a propriedade <AspNetCoreHostingModel> não
estiver presente no arquivo, o valor padrão será OutOfProcess .
Defina o valor da propriedade <AspNetCoreHostingModel> como OutOfProcess (a hospedagem no processo é
definida com InProcess ):
<PropertyGroup>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
</PropertyGroup>
A propriedade InheritInChildApplications é definida como false para indicar que as configurações especificadas
no elemento <location> não são herdadas por aplicativos que residem em um subdiretório do aplicativo.
Quando um aplicativo é implantado no Serviço de Aplicativo do Azure, o caminho stdoutLogFile é definido para
\\?\%home%\LogFiles\stdout . O caminho salva logs de stdout para a pasta LogFiles, que é um local criado
automaticamente pelo serviço.
Para saber mais sobre a configuração de subaplicativos do IIS, confira Hospedar o ASP.NET Core no Windows
com o IIS.
Atributos do elemento aspNetCore
ATRIBUTO DESCRIÇÃO PADRÃO
ATRIBUTO DESCRIÇÃO PADRÃO
WARNING
As variáveis de ambiente definidas nesta seção são conflitantes com as variáveis de ambiente do sistema definidas com o
mesmo nome. Quando a variável de ambiente é definida no arquivo web.config e no nível do sistema do Windows, o valor do
arquivo web.config fica anexado ao valor da variável de ambiente do sistema (por exemplo,
ASPNETCORE_ENVIRONMENT: Development;Development ), o que impede a inicialização do aplicativo.
O exemplo a seguir define duas variáveis de ambiente. ASPNETCORE_ENVIRONMENT configura o ambiente do aplicativo
para Development . Um desenvolvedor pode definir esse valor temporariamente no arquivo web.config para forçar
o carregamento da Página de Exceções do Desenvolvedor ao depurar uma exceção de aplicativo. CONFIG_DIR é um
exemplo de uma variável de ambiente definida pelo usuário, em que o desenvolvedor escreveu código que lê o
valor de inicialização para formar um caminho no qual carregar o arquivo de configuração do aplicativo.
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile="\\?\%home%\LogFiles\stdout"
hostingModel="InProcess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
<environmentVariable name="CONFIG_DIR" value="f:\application_config" />
</environmentVariables>
</aspNetCore>
<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>
NOTE
Em vez de configurar o ambiente diretamente no web.config, você pode incluir a propriedade <EnvironmentName> no perfil
de publicação (.pubxml) ou no perfil de projeto. Esta abordagem define o ambiente no arquivo web.config quando o projeto é
publicado:
<PropertyGroup>
<EnvironmentName>Development</EnvironmentName>
</PropertyGroup>
WARNING
Defina a variável de ambiente apenas ASPNETCORE_ENVIRONMENT para Development em servidores de preparo e de teste
que não estão acessíveis a redes não confiáveis, tais 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 em shutdownTimeLimit , o Módulo do ASP.NET Core
encerra o processo em execução.
Enquanto o arquivo app_offline.htm estiver presente, o Módulo do ASP.NET Core responderá às solicitações
enviando o conteúdo do arquivo app_offline.htm. Quando o arquivo app_offline.htm é removido, a próxima
solicitação inicia o aplicativo.
Ao usar o modelo de hospedagem de fora do processo, talvez o aplicativo não desligue imediatamente se houver
uma conexão aberta. Por exemplo, uma conexão websocket pode atrasar o desligamento do aplicativo.
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="true"
stdoutLogFile="\\?\%home%\LogFiles\stdout">
</aspNetCore>
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile="\\?\%home%\LogFiles\stdout"
hostingModel="InProcess">
<handlerSettings>
<handlerSetting name="debugFile" value="aspnetcore-debug.log" />
<handlerSetting name="debugLevel" value="FILE,TRACE" />
</handlerSettings>
</aspNetCore>
WARNING
Não deixe o log de depuração habilitado na implantação por mais tempo que o necessário para solucionar um problema. O
tamanho do log não é limitado. Deixar o log de depuração habilitado pode esgotar o espaço em disco disponível e causar
falha no servidor ou no serviço de aplicativo.
Veja Configuração com web.config para obter um exemplo do elemento aspNetCore no arquivo web.config.
Recursos adicionais
Hospedar o ASP.NET Core no Windows com o IIS
Repositório do GitHub do Módulo do ASP.NET Core (origem de referência)
Módulos do IIS com o ASP.NET Core
Solucionar problemas no ASP.NET Core no Serviço
de Aplicativo do Azure
21/01/2019 • 22 minutes to read • Edit Online
IMPORTANT
Versões prévias do ASP.NET Core com o Serviço de Aplicativo do Azure
Versões prévias do ASP.NET Core não são implantadas para o Serviço de Aplicativo do Azure por padrão. Para
hospedar um aplicativo que usa uma versão prévia do ASP.NET Core, veja Implantar versão prévia do ASP.NET Core
para o Serviço de Aplicativo do Azure.
Este artigo fornece instruções sobre como diagnosticar um problema de inicialização do aplicativo ASP.NET
Core ao usar as ferramentas de diagnóstico do Serviço de Aplicativo do Azure. Para obter conselhos sobre
solução de problemas adicionais, consulte Visão geral de diagnóstico do Serviço de Aplicativo do Azure e
Como: monitorar aplicativos no Serviço de Aplicativo do Azure na documentação do Azure.
WARNING
Falha ao desabilitar o log de stdout pode levar a falhas de aplicativo ou de servidor. Não há limites para o tamanho do
arquivo de log ou para o número de arquivos de log criados. Somente use o log de stdout para solucionar problemas
de inicialização de aplicativo.
Para registro em log geral em um aplicativo ASP.NET Core após a inicialização, use uma biblioteca de registro em log
que limita o tamanho do arquivo de log e realiza a rotação de logs. Para obter mais informações, veja provedores de
log de terceiros.
WARNING
A falha ao desabilitar o log de depuração pode levar a falhas de aplicativo ou de servidor. Não há nenhum limite no
tamanho do arquivo de log. Somente use o log de depuração para solucionar problemas de inicialização de aplicativo.
Para registro em log geral em um aplicativo ASP.NET Core após a inicialização, use uma biblioteca de registro em log
que limita o tamanho do arquivo de log e realiza a rotação de logs. Para obter mais informações, veja provedores de
log de terceiros.
Depuração remota
Confira os seguintes tópicos:
Seção "Depuração remota de aplicativos Web" de "Solucionar problemas de um aplicativo Web no
Serviço de Aplicativo do Azure usando o Visual Studio" (documentação do Azure)
Depuração Remota do ASP.NET Core no IIS no Azure no Visual Studio 2017 (documentação do Visual
Studio)
Informações do aplicativo
O Application Insights fornece telemetria de aplicativos hospedados no Serviço de Aplicativo do Azure,
incluindo recursos de relatório e de registro de erros em log. O Application Insights só pode relatar erros
ocorridos depois que o aplicativo é iniciado quando os recursos de registro em log do aplicativo se tornam
disponíveis. Para obter mais informações, veja Application Insights para ASP.NET Core.
Folhas de monitoramento
As folhas de monitoramento fornecem uma experiência alternativa de solução de problemas para os métodos
descritos anteriormente no tópico. Essas folhas podem ser usadas para diagnosticar erros da série 500.
Verifique se as Extensões do ASP.NET Core estão instaladas. Se as extensões não estiverem instaladas,
instale-as manualmente:
1. Na seção de folha FERRAMENTAS DE DESENVOLVIMENTO, selecione a folha Extensões.
2. As Extensões do ASP.NET Core devem aparecer na lista.
3. Se as extensões não estiverem instaladas, selecione o botão Adicionar.
4. Escolha as Extensões do ASP.NET Core da lista.
5. Selecione OK para aceitar os termos legais.
6. Selecione OK na folha Adicionar extensão.
7. Uma mensagem pop-up informativa indica quando as extensões são instaladas com êxito.
Se o registro em log de stdout não estiver habilitado, siga estas etapas:
1. No portal do Azure, selecione a folha Ferramentas Avançadas na área FERRAMENTAS DE
DESENVOLVIMENTO. Selecione o botão Ir→. O console do Kudu é aberto em uma nova janela ou 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 arquivo web.config na parte
inferior da lista.
4. Clique no ícone de lápis ao lado do arquivo web.config.
5. Defina stdoutLogEnabled para true e altere o caminho stdoutLogFile para
\\?\%home%\LogFiles\stdout .
6. Selecione Salvar para salvar o arquivo web.config atualizado.
Prossiga para ativar o log de diagnóstico:
1. No portal do Azure, selecione a folha Logs de diagnóstico.
2. Selecione a opção Ligado para Log de Aplicativo (Sistema de arquivos) e Mensagens de erro
detalhadas. Selecione o botão Salvar na parte superior da folha.
3. Para incluir o rastreamento de solicitação com falha, também conhecido como FREB (Buffer de Evento de
Solicitação com Falha), selecione a opção Ligado para o Rastreamento de solicitação com falha.
4. Selecione a folha Fluxo de log, que é listada imediatamente sob a folha Logs de diagnóstico no portal.
5. Faça uma solicitação ao aplicativo.
6. Dentro dos dados de fluxo de log, a causa do erro é indicada.
Sempre desabilite o registro em log de stdout após concluir a solução de problemas. Veja as instruções na
seção log de stdout do Módulo do ASP.NET Core.
Para exibir os logs de rastreamento de solicitação com falha (logs FREB ):
1. Navegue até a folha Diagnosticar e resolver problemas no portal do Azure.
2. Selecione Logs de Rastreamento de Solicitação com Falha da área FERRAMENTAS DE SUPORTE
da barra lateral.
Confira a seção de Rastreamentos de solicitação com falha no tópico Habilitar o log de diagnósticos para
aplicativos Web no Serviço de Aplicativo do Azure e as 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, veja Habilitar log de diagnósticos para aplicativos Web no Serviço de Aplicativo
do Azure.
WARNING
Falha ao desabilitar o log de stdout pode levar a falhas de aplicativo ou de servidor. Não há limites para o tamanho do
arquivo de log ou para o número de arquivos de log criados.
Para registro em log de rotina em um aplicativo ASP.NET Core, use uma biblioteca de registro em log que limita o
tamanho do arquivo de log e realiza a rotação de logs. Para obter mais informações, veja provedores de log de
terceiros.
Recursos adicionais
Tratar erros 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 não disponível" em
seus aplicativos Web do Azure
Solucionar problemas de desempenho lento do aplicativo Web no Serviço de Aplicativo do Azure
Perguntas frequentes sobre o 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)
Referência de erros comuns para o Serviço de
Aplicativo do Azure e o IIS com o ASP.NET Core
21/01/2019 • 27 minutes to read • Edit Online
IMPORTANT
Versões prévias do ASP.NET Core com o Serviço de Aplicativo do Azure
Versões prévias do ASP.NET Core não são implantadas para o Serviço de Aplicativo do Azure por padrão. Para hospedar um
aplicativo que usa uma versão prévia do ASP.NET Core, veja Implantar versão prévia do ASP.NET Core para o Serviço de
Aplicativo do Azure.
Solução de problemas:
Para uma implantação dependente da estrutura x86 ( <PlatformTarget>x86</PlatformTarget> ), habilite o pool de
aplicativos de IIS para aplicativos de 32 bits. No Gerenciador do IIS, abra as Configurações Avançadas do pool
de aplicativos e defina Habilitar Aplicativos de 32 Bits como Verdadeiro.
Bem-vindo
Bem-vindo ao guia de ciclo de vida de desenvolvimento do Azure para .NET. Este guia apresenta os conceitos
básicos da criação de um ciclo de vida de desenvolvimento para o Azure usando processos e ferramentas do .NET.
Depois de concluí-lo, você aproveitará os benefícios de uma cadeia de ferramentas madura do DevOps.
O Azure tem várias interfaces para provisionamento e gerenciamento de recursos, como o portal do Azure, CLI do
Azure, Azure PowerShell, nuvem do Azure Shelle o Visual Studio. Este guia usa uma abordagem minimalista e usa
o Azure Cloud Shell sempre que possível para reduzir as etapas necessárias. No entanto, o portal do Azure deve
ser usado para algumas partes.
Pré-requisitos
As assinaturas a seguir são necessárias:
Azure — se você não tiver uma conta Obtenha uma avaliação gratuita.
Os serviços do Azure DevOps — sua assinatura de DevOps do Azure e a organização é criado no capítulo 4.
GitHub — se você não tiver uma conta Inscreva-se gratuitamente.
As ferramentas a seguir são necessárias:
Git — um entendimento fundamental do Git é recomendado para este guia. Examine os documentação do
Git, especificamente git remoto e git push.
SDK do .NET core — versão 2.1.300 ou posterior é necessário para compilar e executar o aplicativo de
exemplo. Se o Visual Studio é instalado com o desenvolvimento de plataforma cruzada do .NET Core
carga de trabalho, o SDK do .NET Core já está instalada.
Verifique se a instalação do SDK do .NET Core. Abra um shell de comando e execute o seguinte comando:
dotnet --version
O serviço de aplicativo do Azure é plataforma de hospedagem de web do Azure. Implantar um aplicativo web no
serviço de aplicativo do Azure pode ser feito manualmente ou por um processo automatizado. Esta seção do guia
discute métodos de implantação que podem ser disparados manualmente ou por script usando a linha de
comando ou disparada manualmente usando o Visual Studio.
Nesta seção, você vai realizar as seguintes tarefas:
Baixe e compile o aplicativo de exemplo.
Crie um aplicativo serviço de aplicativo Web usando o Azure Cloud Shell.
Implante o aplicativo de exemplo no Azure usando Git.
Implante uma alteração no aplicativo usando o Visual Studio.
Adicione um slot de preparo para o aplicativo web.
Implante uma atualização para o slot de preparo.
Troca os slots de preparo e produção.
Observação: Os usuários do Linux/macOS devem fazer as alterações apropriadas para caminhos, por
exemplo, usando a barra invertida ( / ) em vez de barra invertida ( \ ).
2. Alterar pasta de trabalho para o leitor de feeds simples pasta que foi criada.
cd .\simple-feed-reader\SimpleFeedReader
dotnet build
4. Execute o aplicativo.
dotnet run
5. Abra um navegador e navegue até http://localhost:5000 . O aplicativo permite que você digite ou cole um
URL de feed de distribuição e exibir uma lista de itens de notícias.
6. Quando estiver satisfeito o aplicativo está funcionando corretamente, desligá-lo pressionando Ctrl+C no
shell de comando.
b. Crie um grupo de recursos. Grupos de recursos fornecem um meio para agregar os recursos do Azure
para ser gerenciado como um grupo.
O az comando invoca o CLI do Azure. A CLI pode ser executada localmente, mas usá-lo no Cloud Shell
economiza tempo e configuração.
c. Crie um plano de serviço de aplicativo na camada S1. Um plano do serviço de aplicativo é um
agrupamento de aplicativos web que compartilham o mesmo tipo de preço. A camada S1 não é gratuita,
mas ela é necessária para o recurso de slots de preparo.
d. Crie recurso de aplicativo web usando o plano de serviço de aplicativo no mesmo grupo de recursos.
f. Configurar o aplicativo web para aceitar as implantações do Git local e a exibição de URL de implantação
do Git. Anote essa URL para referência posterior.
echo Git deployment URL: $(az webapp deployment source config-local-git --name $webappname --resource-
group AzureTutorial --query url --output tsv)
g. Exibição de URL do aplicativo web. Navegue até essa URL para ver o aplicativo web em branco. Anote
essa URL para referência posterior.
3. Usando um shell de comando no computador local, navegue até a pasta do projeto do aplicativo web (por
exemplo, .\simple-feed-reader\SimpleFeedReader ). Execute os seguintes comandos para configurar o Git
para enviar por push para a URL de implantação:
a. Adicione a URL remota no repositório local.
b. Enviar por push o local mestre ramificar para o azure prod do remote mestre branch.
Você será solicitado para as credenciais de implantação que você criou anteriormente. Observe a saída no
shell de comando. Azure cria o aplicativo ASP.NET Core remotamente.
4. Em um navegador, navegue até a URL do aplicativo Web e observe o aplicativo foi compilado e implantado.
Alterações adicionais podem ser confirmadas no repositório Git local com git commit . Essas alterações são
enviadas por push para o Azure com o anterior git push comando.
O aplicativo já foi implantado no shell de comando. Vamos usar ferramentas integradas do Visual Studio para
implantar uma atualização para o aplicativo. Nos bastidores, o Visual Studio realiza a mesma coisa, como a
ferramentas de linha de comando, mas dentro da interface de usuário familiar do Visual Studio.
1. Abra SimpleFeedReader.sln no Visual Studio.
2. No Gerenciador de soluções, abra Pages\Index.cshtml. Alteração <h2>Simple Feed Reader</h2> para
<h2>Simple Feed Reader - V2</h2> .
Visual Studio compila e implanta o aplicativo do Azure. Navegue até a URL do aplicativo web. Validar que o <h2>
modificação do elemento está ativo.
Slots de implantação
Slots de implantação oferecer suporte a preparação de alterações sem afetar o aplicativo em execução na
produção. Depois que a versão de preparada do aplicativo é validada por uma equipe de garantia de qualidade,
slots de preparo e produção podem ser trocados. O aplicativo no ambiente de preparo é promovido para produção
dessa maneira. As etapas a seguir criar um slot de preparo, implantar algumas alterações nele e trocar o slot de
preparo em produção após a verificação.
1. Entrar para o Azure Cloud Shell, se não estiver conectado.
2. Crie o slot de preparo.
a. Criar um slot de implantação com o nome preparo.
az webapp deployment slot create --name $webappname --resource-group AzureTutorial --slot staging
b. Configurar o slot de preparo para usar a implantação do Git local e obter o preparo URL de implantação.
Anote essa URL para referência posterior.
echo Git deployment URL for staging: $(az webapp deployment source config-local-git --name $webappname -
-resource-group AzureTutorial --slot staging --query url --output tsv)
c. Exiba a URL do slot de preparo. Navegue até a URL para ver o slot de preparo vazio. Anote essa URL
para referência posterior.
3. Em um editor de texto ou o Visual Studio, modifique Pages novamente para que o <h2> elemento lê
<h2>Simple Feed Reader - V3</h2> e salve o arquivo.
4. Confirmar o arquivo para o repositório Git local, usando o alterações página no Visual Studio Team
Explorer guia, ou digitando o seguinte usando o shell de comando do computador local:
git commit -a -m "upgraded to V3"
5. Usando o shell de comando do computador local, adicione a URL de implantação de preparo como um Git
remoto e enviar por push as alterações confirmadas:
a. Adicione a URL remota para a preparação para o repositório Git local.
b. Enviar por push o local mestre ramificar para o azure preparo do remote mestre branch.
az webapp deployment slot swap --name $webappname --resource-group AzureTutorial --slot staging
No capítulo anterior, você criou um repositório Git local para o aplicativo de leitor de Feed simples. Neste capítulo,
você irá publicar esse código em um repositório do GitHub e construir um pipeline de serviços de DevOps do
Azure usando os Pipelines do Azure. O pipeline permite compilações contínuas e implantações do aplicativo.
Qualquer confirmação para o repositório GitHub dispara uma compilação e uma implantação em slot de preparo
do aplicativo Web do Azure.
Nesta seção, você concluirá as seguintes tarefas:
Publicar o código do aplicativo no GitHub
Desconectar-se a implantação do Git local
Criar uma organização de DevOps do Azure
Criar um projeto de equipe nos serviços de DevOps do Azure
Criar uma definição de compilação
Criar um pipeline de lançamento
Confirmar alterações no GitHub e implantar automaticamente no Azure
Examinar o pipeline de Pipelines do Azure
3. Selecione sua conta na proprietário lista suspensa e insira leitor de feeds simples no nome do repositório
caixa de texto.
4. Clique o criar repositório botão.
5. Abra o shell de comando do seu computador local. Navegue até o diretório no qual o leitor de feeds simples
repositório Git é armazenado.
6. Renomear existente origin remoto para upstream. Execute o seguinte comando:
7. Adicione um novo origem remoto que aponta para a sua cópia do repositório no GitHub. Execute o
seguinte comando:
git remote add origin https://github.com/<GitHub_username>/simple-feed-reader/
8. Publica seu repositório Git local para o repositório GitHub recém-criado. Execute o seguinte comando:
2. Clique em opções de implantação. Um novo painel é exibido. Clique em desconectar para remover a
configuração de controle de código-fonte Git local que foi adicionada no capítulo anterior. Confirme a
operação de remoção clicando o Sim botão.
3. Navegue até a mywebapp < unique_number > o serviço de aplicativo. Como lembrete, caixa de pesquisa
do portal pode ser usada para localizar rapidamente o serviço de aplicativo.
4. Clique em opções de implantação. Um novo painel é exibido. Clique em desconectar para remover a
configuração de controle de código-fonte Git local que foi adicionada no capítulo anterior. Confirme a
operação de remoção clicando o Sim botão.
4. Se a autenticação de dois fatores é ativada em sua conta do GitHub, um token de acesso pessoal é
necessário. Clique nesse caso, o autorizar com um token de acesso pessoal do GitHub link. Consulte a
instruções oficiais de criação de token de acesso pessoal do GitHub para obter ajuda. Somente o repositório
escopo das permissões é necessária. Caso contrário, clique o autorizar usando OAuth botão.
5. Quando solicitado, entre sua conta do GitHub. Em seguida, selecione a autorização para conceder acesso a
sua organização de DevOps do Azure. Se for bem-sucedido, um novo ponto de extremidade de serviço é
criado.
6. Clique no botão de reticências ao lado de repositório botão. Selecione o < GitHub_username > / leitor de
feeds simples repositório na lista. Clique o selecionar botão.
7. Selecione o mestre ramificar a branch padrão para compilações de manuais e programadas lista
suspensa. Clique no botão Continue (Continuar). Página seleção de modelo é exibido.
Criar a definição de compilação
1. Na página seleção do modelo, insira ASP.NET Core na caixa de pesquisa:
2. Os resultados da pesquisa de modelo são exibidos. Passe o mouse sobre o ASP.NET Core modelo e clique
no aplicar botão.
3. O tarefas guia da definição de compilação é exibida. Clique na guia Gatilhos .
4. Verifique as habilitar a integração contínua caixa. Sob o filtros de ramificação seção, confirme se o
tipo lista suspensa é definida como Include. Defina as especificação de Branch lista suspensa para
mestre.
Essas configurações fazem com que uma compilação disparar quando qualquer alteração é enviada por
push para o mestre branch do repositório do GitHub. Integração contínua é testada na confirmar alterações
no GitHub e implantar automaticamente para o Azure seção.
5. Clique o salvar e enfileirar botão e, em seguida, selecione o salvar opção:
3. Os resultados da pesquisa de modelo são exibidos. Passe o mouse sobre o implantação de serviço de
aplicativo do Azure com o Slot modelo e clique no aplicar botão. O Pipeline guia do pipeline de
lançamento é exibida.
Com essa opção habilitada, uma implantação ocorre sempre que uma nova compilação está disponível.
12. Um gatilho de implantação contínua painel aparece à direita. Clique no botão de alternância para
habilitar o recurso. Não é necessário habilitar o gatilho de solicitação de Pull.
13. Clique o Add na lista suspensa a criar filtros de ramificação seção. Escolha o ramificação de padrão da
definição de compilação opção. Esse filtro faz com que a versão disparar apenas para uma compilação do
repositório do GitHub mestre branch.
14. Clique no botão Salvar. Clique o Okey botão na resultante salvar caixa de diálogo modal.
15. Clique o ambiente 1 caixa. Uma ambiente painel aparece à direita. Alterar o ambiente 1 texto na nome
do ambiente caixa de texto produção.
5. Enviar por push a alteração na mestre ramificar para o origem remoto do seu repositório GitHub:
A compilação é disparada, como integração contínua está habilitada na definição de build gatilhos guia:
6. Navegue até a enfileirado guia da Pipelines do Azure > compilações página nos serviços de DevOps
do Azure. Compilação enfileirada mostra a ramificação e confirme que disparou a compilação:
7. A compilação for bem-sucedida, ocorre uma implantação do Azure. Navegue até o aplicativo no navegador.
Observe que o texto "V4" é exibido no título:
Examinar o pipeline de Pipelines do Azure
Definição de compilação
Uma definição de compilação foi criada com o nome MyFirstProject do ASP.NET Core-CI. Após a conclusão, a
compilação produz um . zip arquivo, incluindo os ativos a serem publicadas. O pipeline de lançamento implanta
esses ativos do Azure.
A definição de compilação tarefas guia lista as etapas individuais que está sendo usadas. Há cinco tarefas de
compilação.
1. Restaure — executa o dotnet restore comando restaurar pacotes do NuGet do aplicativo. O pacote
padrão feed usado é nuget.org.
2. Crie — executa o comando para compilar o código do aplicativo. Isso
dotnet build --configuration release
--configuration opção é usada para produzir uma versão otimizada do código, que é adequado para
implantação em um ambiente de produção. Modificar a BuildConfiguration variável na definição de
compilação variáveis guia se, por exemplo, uma configuração de depuração é necessária.
3. Teste — executa o
dotnet test --configuration release --logger trx --results-directory <local_path_on_build_agent>
comando para executar testes de unidade do aplicativo. Testes de unidade são executados em qualquer
linguagem c# projeto de correspondência a **/*Tests/*.csproj padrão glob. Resultados de teste são salvos
em um trx arquivo no local especificado pelo --results-directory opção. Se qualquer teste falhar, a
compilação falhará e não será implantada.
NOTE
Para verificar se o trabalho de testes de unidade, modifique SimpleFeedReader.Tests\Services\NewsServiceTests.cs
propositadamente quebrar um dos testes. Por exemplo, altere Assert.True(result.Count > 0); à
Assert.False(result.Count > 0); no Returns_News_Stories_Given_Valid_Uri método. Confirme e envie a
alteração ao GitHub. A compilação é disparada e falha. O status do pipeline de compilação muda para falha. Reverta
a alteração, a confirmação e o envio por push novamente. A compilação for bem-sucedida.
Um resumo dessa compilação específica é exibido. Clique o artefatos guia e observe o drop produzida pela
compilação de pasta é listada:
Use o Baixe e explorar links para inspecionar os artefatos publicados.
Pipeline de lançamento
Um pipeline de lançamento foi criado com o nome MyFirstProject do ASP.NET Core-CD:
Os dois principais componentes do pipeline de lançamento são as artefatos e o ambientes. Clicando na caixa na
artefatos seção revela o seguinte painel:
O fonte (definição de compilação) valor representa a definição de compilação ao qual esse pipeline de versão
está vinculada. O . zip arquivo produzido por uma execução bem-sucedida da definição de compilação é fornecido
para o produção ambiente para implantação no Azure. Clique o 1 fase, 2 tarefas link na produção caixa do
ambiente para exibir as tarefas de pipeline de lançamento:
O pipeline de lançamento consiste em duas tarefas: implantar o serviço de aplicativo do Azure ao Slot e gerenciar
o serviço de aplicativo Azure - Slot de troca. Clicar a primeira tarefa revela a configuração de tarefa a seguir:
A assinatura do Azure, o tipo de serviço, o nome do aplicativo web, o grupo de recursos e o slot de implantação
são definidos na tarefa de implantação. O pacote ou pasta caixa de texto contém o . zip caminho do arquivo a ser
extraído e implantado para o preparo slot do mywebapp<exclusivo número> aplicativo web.
Clicar a tarefa de troca de slot revela a configuração de tarefa a seguir:
A assinatura, grupo de recursos, tipo de serviço, nome do aplicativo web e detalhes do slot de implantação são
fornecidas. O troca com produção caixa de seleção está marcada. Consequentemente, os bits implantados para o
preparo slot são trocadas no ambiente de produção.
Leitura adicional
Criar seu primeiro pipeline com o Azure Pipelines
Projeto de compilação e .NET Core
Implantar um aplicativo web com Pipelines do Azure
Monitorar e depurar
12/12/2018 • 10 minutes to read • Edit Online
Tendo implantado o aplicativo e criado um pipeline de DevOps, é importante entender como monitorar e
solucionar problemas com o aplicativo.
Nesta seção, você concluirá as seguintes tarefas:
Localizar básicos de monitoramento e solução de problemas de dados no portal do Azure
Saiba como o Azure Monitor fornece uma análise mais profunda das métricas entre todos os serviços do Azure
Conectar o aplicativo web com o Application Insights para a criação de perfil de aplicativo
Ativar o registro em log e saiba onde baixar logs
Stream logs em tempo real
Saiba onde configurar alertas
Saiba mais sobre aplicativos de web do serviço de aplicativo do Azure depuração remotos.
Monitoramento avançado
O Azure Monitor é o serviço centralizado para todas as métricas de monitoramento e configuração de alertas em
todos os serviços do Azure. Dentro do Azure Monitor, os administradores podem controlar o desempenho
granularmente e identificar tendências. Cada serviço do Azure oferece seu próprio conjunto de métricas para o
Azure Monitor.
3. Selecione o criar novo recurso botão de opção. Use o nome de recurso padrão e selecione o local para o
recurso Application Insights. O local não precisa corresponder ao que um aplicativo web.
Como o aplicativo é usado, os dados acumulam. Selecione Refresh para recarregar a folha com novos dados.
O Application Insights fornece informações úteis do lado do servidor sem nenhuma configuração adicional. Para
obter o máximo valor do Application Insights instrumentar seu aplicativo com o SDK do Application Insights.
Quando configurado corretamente, o serviço fornece monitoramento de ponta a ponta entre o servidor web e o
navegador, incluindo desempenho do lado do cliente. Para obter mais informações, consulte o documentação do
Application Insights.
Registrando em log
Logs de servidor e aplicativo da Web estão desabilitados por padrão no serviço de aplicativo do Azure. Habilite os
logs com as seguintes etapas:
1. Abra o portal do Azuree navegue até a mywebapp<unique_number> o serviço de aplicativo.
2. No menu à esquerda, role para baixo até a monitoramento seção. Selecione logs de diagnóstico.
3. Ative log de aplicativo (Filesystem ). Se solicitado, clique na caixa para instalar as extensões para habilitar
o registro em log no aplicativo web do aplicativo.
4. Definir log do servidor Web à sistema de arquivos.
5. Insira o período de retenção em dias. Por exemplo, 30.
6. Clique em Salvar.
Logs do servidor (serviço de aplicativo) do ASP.NET Core e da web são gerados para o aplicativo web. Eles podem
ser baixados usando as informações de FTP/FTPS exibidas. A senha é o mesmo que as credenciais de implantação
criadas anteriormente neste guia. Os logs podem ser transmitido diretamente ao seu computador local com o
PowerShell ou CLI do Azure. Os logs também podem ser exibidos no Application Insights.
Streaming de log
Logs do servidor web e de aplicativo podem ser transmitidos em tempo real por meio do portal.
1. Abra o portal do Azuree navegue até a mywebapp<unique_number> o serviço de aplicativo.
2. No menu à esquerda, role para baixo até a Monitoring seção e selecione fluxo de Log.
Os logs também podem ser transmitido por meio da CLI do Azure ou o Azure PowerShell, incluindo por meio do
Cloud Shell.
Alertas
O Azure Monitor também fornece alertas em tempo real com base em métricas, eventos administrativos e outros
critérios.
Observação: No momento, alertas em métricas do aplicativo web só está disponível no serviço alertas
(clássico ).
O alertas de serviço (clássico) pode ser encontrada no Azure Monitor ou sob o monitoramento seção das
configurações de serviço de aplicativo.
Depuração ao vivo
O serviço de aplicativo do Azure pode ser depurado remotamente com o Visual Studio quando os logs não
fornecer informações suficientes. No entanto, a depuração remota exige o aplicativo a ser compilada com símbolos
de depuração. Depuração não deve ser feita em produção, exceto como último recurso.
Conclusão
Nesta seção, você concluiu as seguintes tarefas:
Localizar básicos de monitoramento e solução de problemas de dados no portal do Azure
Saiba como o Azure Monitor fornece uma análise mais profunda das métricas entre todos os serviços do Azure
Conectar o aplicativo web com o Application Insights para a criação de perfil de aplicativo
Ativar o registro em log e saiba onde baixar logs
Stream logs em tempo real
Saiba onde configurar alertas
Saiba mais sobre aplicativos de web do serviço de aplicativo do Azure depuração remotos.
Leitura adicional
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Referência de erros comuns para o Serviço de Aplicativo do Azure e o IIS com o ASP.NET Core
Monitorar o desempenho do aplicativo web do Azure com o Application Insights
Habilitar log de diagnósticos para aplicativos Web no Serviço de Aplicativo do Azure
Solucionar problemas de um aplicativo Web no Serviço de Aplicativo do Azure usando o Visual Studio
Criar alertas de métrica clássicos no Azure Monitor para serviços do Azure - portal do Azure
Próximas etapas
12/12/2018 • 3 minutes to read • Edit Online
Neste guia, você criou um pipeline de DevOps para um aplicativo de exemplo do ASP.NET Core. Parabéns!
Esperamos que você tenha gostado de aprendizado publicar aplicativos web ASP.NET Core no serviço de
aplicativo do Azure e automatizar a integração contínua das alterações.
Além de hospedagem na web e DevOps, o Azure tem uma ampla gama de serviços de plataforma-como um
serviço (PaaS ) útil para desenvolvedores do ASP.NET Core. Esta seção fornece uma visão geral de alguns dos
serviços mais comumente usados.
Identidade
O Azure Active Directory e Azure Active Directory B2C são ambos os serviços de identidade. O Azure Active
Directory foi projetado para cenários empresariais e permite a colaboração do Azure AD B2B (business-to-
business), enquanto o Azure Active Directory B2C é pretendidos cenários de negócios para o cliente, incluindo
rede social entrar.
Celular
Os Hubs de notificação é um mecanismo de notificação por push multiplataforma e dimensionável para enviar
rapidamente milhões de mensagens para aplicativos em execução em vários tipos de dispositivos.
Infraestrutura da Web
O serviço de contêiner do Azure gerencia seu ambiente Kubernetes hospedado, tornando rápido e fácil de
implantar e gerenciar aplicativos em contêineres sem conhecimento de orquestração de contêiner.
O Azure Search é usado para criar uma solução de pesquisa empresarial sobre conteúdo privada e heterogênea.
O Service Fabric é uma plataforma de sistemas distribuídos que torna mais fácil empacotar, implantar e gerenciar
escalonável e confiáveis microsserviços e contêineres.
Hospedar o ASP.NET Core no Windows com o IIS
01/02/2019 • 50 minutes to read • Edit Online
Configuração do aplicativo
Habilitar os componentes de IISIntegration
Um Program.cs típico chama CreateDefaultBuilder para começar a configurar um host:
services.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = false;
});
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
});
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
Para configurar o Módulo do ASP.NET Core corretamente, 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.
IMPORTANT
Se o pacote de hospedagem for instalado antes do IIS, a instalação do pacote deverá ser reparada. Execute o instalador
do pacote de hospedagem novamente depois de instalar o IIS.
WARNING
Alguns instaladores contêm versões de lançamento que atingiram o EOL (fim da vida útil) e não têm mais suporte da
Microsoft. Para saber mais, confira a política de suporte.
NOTE
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.
WARNING
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.
Implantação da Web fora do Visual Studio
Também é possível usar a Implantação da Web fora do Visual Studio na linha de comando. Para obter mais
informações, consulte Ferramenta de Implantação da Web.
Alternativas à Implantação da Web
Use qualquer um dos vários métodos para mover o aplicativo para o sistema host, como cópia manual, Xcopy,
Robocopy ou PowerShell.
Para obter mais informações sobre a implantação do ASP.NET Core no IIS, consulte a seção Recursos de
implantação para administradores do IIS.
Navegar no site
$pathToApp = 'PATH_TO_APP'
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. Para obter
mais informações, consulte Proteção de dados do ASP.NET Core.
Diretórios virtuais
Diretórios virtuais IIS não são compatíveis com aplicativos ASP.NET Core. Um aplicativo pode ser hospedado
como um subaplicativo.
Subaplicativos
Um aplicativo ASP.NET Core pode ser hospedado como um subaplicativo IIS (sub-app). A parte do caminho
do subaplicativo se torna parte da URL raiz do aplicativo.
Um subaplicativo não deve 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:
Links de ativos estáticos dentro do subaplicativo devem usar a notação de sinal de til e barra ( ~/ ). A notação
de sinal de til e barra aciona um Auxiliar de Marca para preceder a base de caminho do subaplicativo ao link
relativo renderizado. Para um subaplicativo no /subapp_path , uma imagem vinculada com src="~/image.png"
é renderizada como src="/subapp_path/image.png" . O Middleware de Arquivo Estático do aplicativo raiz não
processa a solicitação de arquivo estático. A solicitação é processada pelo Middleware de Arquivo Estático do
subaplicativo.
Se um atributo de ativo estático src for definido como um caminho absoluto (por exemplo,
src="/image.png" ), o link será renderizado sem a base de caminho do subaplicativo. O Middleware de
Arquivos Estáticos do aplicativo raiz tenta fornecer o ativo do webroot da raiz do aplicativo, que resulta em
uma resposta 404 – Não encontrado, a menos que o ativo estático esteja disponível no aplicativo raiz.
Para hospedar um aplicativo ASP.NET Core como um subaplicativo em outro aplicativo do ASP.NET Core:
1. Estabeleça um pool de aplicativos para o subaplicativo. Defina a versão do CLR do .NET como Sem
Código Gerenciado.
2. Adicione o site raiz no Gerenciador do IIS com o subaplicativo em uma pasta no site raiz.
3. Clique com o botão direito do mouse na pasta do subaplicativo no Gerenciador do IIS e selecione
Converter em aplicativo.
4. Na caixa de diálogo Adicionar Aplicativo, use o botão Selecionar no Pool de Aplicativos para
atribuir o pool de aplicativos que você criou ao subaplicativo. Selecione OK.
A atribuição de um pool de aplicativos separado para o subaplicativo é um requisito ao usar o modelo de
hospedagem em processo.
Para obter mais informações sobre o modelo de hospedagem em processo e como configurar o módulo do
ASP.NET Core, confira Módulo do ASP.NET Core e Módulo do ASP.NET Core.
Aplicativos ASP.NET Core são configurados para usar outros provedores de configuração. Para obter mais
informações, consulte Configuração.
Pools de aplicativos
O isolamento do pool de aplicativos é determinado pelo modelo de hospedagem:
Hospedagem em processo – Aplicativos são necessários para execução em pools de aplicativos separados.
Hospedagem fora do processo – É recomendável isolar os aplicativos uns dos outros, executando cada
aplicativo em seu próprio pool de aplicativos.
A caixa de diálogo Adicionar Site do IIS usa como padrão um único pool de aplicativos por aplicativo.
Quando um Nome de site é fornecido, 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.
Ao hospedar vários sites em um servidor, é recomendável isolar os aplicativos uns dos outros, executando
cada aplicativo em seu próprio pool de aplicativo. A caixa de diálogo Adicionar Site do IIS usa como padrão
essa configuração. Quando um Nome de site é fornecido, 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
Solucionar problemas de projetos do ASP.NET Core
Introdução ao ASP.NET Core
O site oficial da IIS da Microsoft
Biblioteca de conteúdo técnico do Windows Server
HTTP/2 no IIS
Módulo do ASP.NET Core
30/01/2019 • 43 minutes to read • Edit Online
Por Tom Dykstra, Rick Strahl, Chris Ross, Rick Anderson, Sourabh Shirhatti, Justin Kotalik e Luke
Latham
O módulo do ASP.NET Core é um módulo nativo do IIS que se conecta ao pipeline do IIS para:
Hospedar um aplicativo ASP.NET Core dentro do processo de trabalho do IIS ( w3wp.exe ),
chamado o modelo de hospedagem no processo.
Encaminhar solicitações da Web a um aplicativo ASP.NET Core de back-end que executa o
servidor Kestrel, chamado o modelo de hospedagem de fora do processo.
Versões do Windows compatíveis:
Windows 7 ou posterior
Windows Server 2008 R2 ou posterior
Ao fazer uma hospedagem em processo, o módulo usa uma implementação de servidor em
processo do IIS, chamado Servidor HTTP do IIS ( IISHttpServer ).
Ao hospedar de fora do processo, o módulo só funciona com o Kestrel. O módulo é incompatível
com HTTP.sys.
Modelos de hospedagem
Modelo de hospedagem em processo
Para configurar um aplicativo para hospedagem em processo, adicione a propriedade
<AspNetCoreHostingModel> ao arquivo de projeto do aplicativo com um valor de InProcess (a
hospedagem de fora do processo é definida com OutOfProcess ):
<PropertyGroup>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
Não há suporte para o modelo de hospedagem em processo para aplicativos ASP.NET Core
direcionados ao .NET Framework.
Se a propriedade <AspNetCoreHostingModel> não estiver presente no arquivo, o valor padrão será
OutOfProcess .
<PropertyGroup>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
</PropertyGroup>
As solicitações chegam da Web para o driver do HTTP.sys no modo kernel. O driver roteia as
solicitações ao IIS na porta configurada do site, normalmente, a 80 (HTTP ) ou a 443 (HTTPS ). O
módulo encaminha as solicitações ao Kestrel em uma porta aleatória do aplicativo, que não seja a
porta 80 ou 443.
O módulo especifica a porta por meio de uma variável de ambiente na inicialização e o
middleware de integração do IIS configura o servidor para escutar em http://localhost:{port} .
Outras verificações são executadas e as solicitações que não se originam do módulo são
rejeitadas. O módulo não é compatível com encaminhamento de HTTPS, portanto, as solicitações
são encaminhadas por HTTP, mesmo se recebidas pelo IIS por HTTPS.
Depois que o Kestrel coleta a solicitação do módulo, a solicitação é enviada por push ao pipeline
do middleware do ASP.NET Core. O pipeline do middleware manipula a solicitação e a passa
como uma instância de HttpContext para a lógica do aplicativo. O middleware adicionado pela
integração do IIS atualiza o esquema, o IP remoto e pathbase para encaminhar a solicitação para
o Kestrel. A resposta do aplicativo é retornada ao IIS, que a retorna por push para o cliente HTTP
que iniciou a solicitação.
Muitos módulos nativos, como a Autenticação do Windows, permanecem ativos. Para saber mais
sobre módulos do IIS ativos com o Módulo do ASP.NET Core, confira Módulos do IIS com o
ASP.NET Core.
O Módulo do ASP.NET Core também pode:
Definir variáveis de ambiente para o processo de trabalho.
Registrar a saída StdOut no armazenamento de arquivo para a solução de problemas de
inicialização.
Encaminhar tokens de autenticação do Windows.
WARNING
As variáveis de ambiente definidas nesta seção são conflitantes com as variáveis de ambiente do sistema
definidas com o mesmo nome. Quando a variável de ambiente é definida no arquivo web.config e no
nível do sistema do Windows, o valor do arquivo web.config fica anexado ao valor da variável de
ambiente do sistema (por exemplo, ASPNETCORE_ENVIRONMENT: Development;Development ), o que
impede a inicialização do aplicativo.
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile="\\?\%home%\LogFiles\stdout"
hostingModel="InProcess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
<environmentVariable name="CONFIG_DIR" value="f:\application_config" />
</environmentVariables>
</aspNetCore>
<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>
NOTE
Em vez de configurar o ambiente diretamente no web.config, você pode incluir a propriedade
<EnvironmentName> no perfil de publicação (.pubxml) ou no perfil de projeto. Esta abordagem define o
ambiente no arquivo web.config quando o projeto é publicado:
<PropertyGroup>
<EnvironmentName>Development</EnvironmentName>
</PropertyGroup>
WARNING
Defina a variável de ambiente apenas ASPNETCORE_ENVIRONMENT para Development em servidores de
preparo e de teste que não estão acessíveis a redes não confiáveis, tais 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 em shutdownTimeLimit , o Módulo do ASP.NET Core encerra o processo em execução.
Enquanto o arquivo app_offline.htm estiver presente, o Módulo do ASP.NET Core responderá às
solicitações enviando o conteúdo do arquivo app_offline.htm. Quando o arquivo app_offline.htm
é removido, a próxima solicitação inicia o aplicativo.
Ao usar o modelo de hospedagem de fora do processo, talvez o aplicativo não desligue
imediatamente se houver uma conexão aberta. Por exemplo, uma conexão websocket pode
atrasar o desligamento do aplicativo.
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="true"
stdoutLogFile="\\?\%home%\LogFiles\stdout"
hostingModel="InProcess">
</aspNetCore>
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="true"
stdoutLogFile="\\?\%home%\LogFiles\stdout">
</aspNetCore>
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile="\\?\%home%\LogFiles\stdout"
hostingModel="InProcess">
<handlerSettings>
<handlerSetting name="debugFile" value="aspnetcore-debug.log" />
<handlerSetting name="debugLevel" value="FILE,TRACE" />
</handlerSettings>
</aspNetCore>
WARNING
Não deixe o log de depuração habilitado na implantação por mais tempo que o necessário para
solucionar um problema. O tamanho do log não é limitado. Deixar o log de depuração habilitado pode
esgotar o espaço em disco disponível e causar falha no servidor ou no serviço de aplicativo.
Veja Configuração com web.config para obter um exemplo do elemento aspNetCore no arquivo
web.config.
Recursos adicionais
Hospedar o ASP.NET Core no Windows com o IIS
Repositório do GitHub do Módulo do ASP.NET Core (origem de referência)
Módulos do IIS com o ASP.NET Core
Suporte ao IIS no tempo de desenvolvimento no
Visual Studio para ASP.NET Core
10/01/2019 • 6 minutes to read • Edit Online
Pré-requisitos
Visual Studio para Windows
Carga de trabalho ASP.NET e desenvolvimento para a Web
Carga de trabalho de desenvolvimento multiplataforma do .NET Core
Certificado de segurança X.509
Habilitar o IIS
1. Navegue para Painel de Controle > Programas > Programas e Recursos > Ativar ou desativar recursos
do Windows (lado esquerdo da tela).
2. Selecione a caixa de seleção Serviços de Informações da Internet.
Configurar o IIS
O IIS deve ter um site configurado com o seguinte:
Um nome do host que corresponda ao nome do host da URL do perfil de inicialização do aplicativo.
Associação para a porta 443, com um certificado atribuído.
Por exemplo, o Nome do host para um site adicionado é definido como "localhost" (o perfil de inicialização
também usará "localhost" posteriormente neste tópico). A porta é definida para "443" (HTTPS ). O Certificado de
Desenvolvimento do IIS Express é atribuído ao site, mas nenhum certificado válido funciona:
Se a instalação do IIS já tiver um site da Web padrão com um nome do host que corresponde ao nome do host
da URL do perfil de inicialização do aplicativo:
Adicione uma associação de porta para a porta 443 (HTTPS ).
Atribua um certificado válido para o site.
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": {
"applicationUrl": "https://localhost/WebApplication1",
"sslPort": 0
}
},
"profiles": {
"IIS": {
"commandName": "IIS",
"launchBrowser": true,
"launchUrl": "https://localhost/WebApplication1",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Executar o projeto
No Visual Studio:
Confirme se a lista de lista suspensa de configuração de compilação está definida para Depurar.
Defina o botão Executar para o perfil do IIS e selecione o botão para iniciar o aplicativo.
O Visual Studio poderá solicitar uma reinicialização se não estiver executando como administrador. Se solicitado,
reinicie o Visual Studio.
Se for usado um certificado de desenvolvimento não confiável, o navegador poderá exigir a criação de uma
exceção para o certificado não confiável.
NOTE
A depuração de uma configuração de Compilação de versão com Apenas Meu Código e otimizações de compilador resulta
em uma experiência inadequada. Por exemplo, os pontos de interrupção não são atingidos.
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
Impor o HTTPS
Módulos do IIS com o ASP.NET Core
21/01/2019 • 11 minutes to read • Edit Online
Módulos nativos
A tabela indica os módulos IIS nativos que funcionam com aplicativos ASP.NET Core e o Módulo do ASP.NET
Core.
CGI Não
CgiModule
†Os tipos de correspondência isFile e isDirectory do módulo de regravação da URL não funcionam com
aplicativos do ASP.NET Core, devido a alterações na estrutura de diretórios.
Módulos gerenciados
Os módulos gerenciados não funcionam com aplicativos do ASP.NET Core hospedados quando a versão do
.NET CLR do pool de aplicativos está definido como Sem Código Gerenciado. O 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 desabilitar módulos com definições de configuração, siga os links na
seção Elementos Filho de IIS <system.webServer>.
Remoção do módulo
Se optar pela remoção de um módulo com uma configuração em web.config, desbloqueie o módulo e
desbloqueie a seção <modules> de web.config primeiro:
1. Desbloqueie o módulo no nível do servidor. Selecione o servidor do IIS na barra lateral Conexões do
Gerenciador do IIS. Abra os Módulos na área IIS. Selecione o módulo na lista. Na barra lateral Ações à
direita, selecione Desbloquear. Se a entrada de ação para o módulo aparece como Bloquear, o módulo
já está desbloqueado e nenhuma ação é necessária. Desbloqueie todos os módulos que você planeja
remover de web.config posteriormente.
2. Implantar o aplicativo sem uma seção <modules> em web.config. Se um aplicativo é implantado com um
web.config que contém a seção <modules> sem ter desbloqueado a seção primeiro no Gerenciador do
IIS, o Configuration Manager gera uma exceção ao tentar desbloquear a seção. Portanto, implante o
aplicativo sem uma seção <modules> .
3. Desbloqueie a seção <modules> de web.config. Na barra lateral Conexões, selecione o site em Sites. Na
área Gerenciamento, abra o Editor de Configuração. Use os controles de navegação para selecionar a
seção system.webServer/modules . Na barra lateral Ações à direita, selecione para Desbloquear a seção.
Se a entrada de ação para a seção do módulo aparece como Bloquear Seção, a seção do módulo já está
desbloqueada e nenhuma ação é necessária.
4. Adicione uma seção <modules> ao arquivo web.config local do aplicativo com um elemento <remove>
para remover o módulo do aplicativo. Adicione vários elementos <remove> para remover vários
módulos. Se alterações a web.config forem feitas no servidor, faça imediatamente as mesmas alterações
no arquivo web.config do projeto localmente. Remover um módulo usando essa abordagem não afeta o
uso do módulo com outros aplicativos no servidor.
<configuration>
<system.webServer>
<modules>
<remove name="MODULE_NAME" />
</modules>
</system.webServer>
</configuration>
Para adicionar ou remover módulos para IIS Express usando o web.config, modifique o applicationHost.config
para desbloquear a seção <modules> :
1. Abra {APPLICATION ROOT }\.vs\config\applicationhost.config.
2. Localize o elemento <section> para módulos do IIS e a altere overrideModeDefault de Deny para
Allow :
<section name="modules"
allowDefinition="MachineToApplication"
overrideModeDefault="Allow" />
4. Após a seção <modules> e módulos individuais serem desbloqueados, você pode adicionar ou remover
módulos do IIS usando o arquivo web.config do aplicativo para executar o aplicativo no IIS Express.
Um módulo do IIS também pode ser removido com Appcmd.exe. Forneça o MODULE_NAME e APPLICATION_NAME
no comando:
Recursos adicionais
Hospedar o ASP.NET Core no Windows com o IIS
Introdução às arquiteturas do IIS: módulos no IIS
Visão geral de módulos do IIS
Personalizando funções e módulos do IIS 7.0
IIS <system.webServer>
Solucionar problemas do ASP.NET Core no IIS
10/01/2019 • 19 minutes to read • Edit Online
O aplicativo falhou ao ser iniciado porque o assembly do aplicativo (.dll) não pôde ser carregado.
Esse erro ocorre quando há uma incompatibilidade de número de bits entre o aplicativo publicado e o
processo w3wp/iisexpress.
Confirme se a configuração de 32 bits do pool de aplicativos está correta:
1. Selecione o pool de aplicativos no Pools de Aplicativos do Gerenciador do IIS.
2. Selecione Configurações Avançadas em Editar Pool de Aplicativos no painel Ações.
3. Defina Habilitar Aplicativos de 32 bits:
Se estiver implantando um aplicativo de 32 bits (x86), defina o valor como True .
Se estiver implantando um aplicativo de 64 bits (x64), defina o valor como False .
Redefinição de conexão
Se um erro ocorrer após os cabeçalhos serem enviados, será tarde demais para o servidor enviar um 500 –
Erro Interno do Servidor no caso de um erro ocorrer. Isso geralmente acontece quando ocorre um erro
durante a serialização de objetos complexos para uma resposta. Esse tipo de erro é exibida como um erro de
redefinição de conexão no cliente. O Log de aplicativo pode ajudar a solucionar esses tipos de erros.
<aspNetCore ...>
<handlerSettings>
<handlerSetting name="debugLevel" value="file" />
<handlerSetting name="debugFile" value="c:\temp\ancm.log" />
</handlerSettings>
</aspNetCore>
Confirme se o caminho especificado para o log existe e se a identidade do pool de aplicativos tem permissões
de gravação no local.
Log de Eventos do Aplicativo
Acesse o Log de Eventos do Aplicativo:
1. Abra o menu Iniciar, procure Visualizador de Eventos e, em seguida, selecione o aplicativo
Visualizador de Eventos.
2. No Visualizador de Eventos, abra o nó Logs do Windows.
3. Selecione Aplicativo para abrir o Log de Eventos do Aplicativo.
4. Procure erros associados ao aplicativo com falha. Os erros têm um valor Módulo AspNetCore do IIS ou
Módulo AspNetCore do IIS Express na coluna Origem.
Execute o aplicativo em um prompt de comando
Muitos erros de inicialização não produzem informações úteis no Log de Eventos do Aplicativo. Você pode
encontrar a causa de alguns erros ao executar o aplicativo em um prompt de comando no sistema de
hospedagem.
Implantação dependente de estrutura
Se o aplicativo é uma implantação dependente de estrutura:
1. Em um prompt de comando, navegue até a pasta de implantação e execute o aplicativo, executando o
assembly do aplicativo com dotnet.exe. No comando a seguir, substitua o nome do assembly do aplicativo
por <assembly_name>: dotnet .\<assembly_name>.dll .
2. A saída do console do aplicativo, mostrando eventuais erros, é gravada na janela do console.
3. Se os erros ocorrerem ao fazer uma solicitação para o aplicativo, faça uma solicitação para o host e a porta
em que o Kestrel escuta. Usando o host e a porta padrão, faça uma solicitação para
http://localhost:5000/ . Se o aplicativo responde normalmente no endereço do ponto de extremidade do
Kestrel, a probabilidade de o problema estar relacionado à configuração de hospedagem é maior e, de
estar relacionado ao aplicativo, menor.
Implantação autocontida
Se o aplicativo é uma implantação autossuficiente:
1. Em um prompt de comando, navegue até a pasta de implantação e execute o arquivo executável do
aplicativo. No comando a seguir, substitua o nome do assembly do aplicativo por <assembly_name>:
<assembly_name>.exe .
2. A saída do console do aplicativo, mostrando eventuais erros, é gravada na janela do console.
3. Se os erros ocorrerem ao fazer uma solicitação para o aplicativo, faça uma solicitação para o host e a porta
em que o Kestrel escuta. Usando o host e a porta padrão, faça uma solicitação para
http://localhost:5000/ . Se o aplicativo responde normalmente no endereço do ponto de extremidade do
Kestrel, a probabilidade de o problema estar relacionado à configuração de hospedagem é maior e, de
estar relacionado ao aplicativo, menor.
Log de stdout do Módulo do ASP.NET Core
Para habilitar e exibir logs de stdout:
1. Navegue até a pasta de implantação do site no sistema de hospedagem.
2. Se a pasta logs não estiver presente, crie-a. Para obter instruções sobre como habilitar o MSBuild para
criar a pasta logs na implantação automaticamente, veja o tópico Estrutura de diretórios.
3. Edite o arquivo web.config. Defina stdoutLogEnabled para true e altere o caminho stdoutLogFile para
apontar para a pasta logs (por exemplo, .\logs\stdout ). stdout no caminho é o prefixo do nome do
arquivo de log. Uma extensão de arquivo, uma ID do processo e um carimbo de data/hora são adicionados
automaticamente quando o log é criado. Usando stdout como o prefixo do nome do arquivo, um arquivo
de log típico é nomeado stdout_20180205184032_5412.log.
4. Verifique se a identidade do pool de aplicativos tem permissões de gravação para a pasta logs.
5. Salve o arquivo web.config atualizado.
6. Faça uma solicitação ao aplicativo.
7. Navegue até a pasta logs. Localize e abra o log de stdout mais recente.
8. Estude o log em busca de erros.
IMPORTANT
Desabilite o registro em log de stdout quando a solução de problemas for concluída.
1. Edite o arquivo web.config.
2. Defina stdoutLogEnabled para false .
3. Salve o arquivo.
WARNING
Falha ao desabilitar o log de stdout pode levar a falhas de aplicativo ou de servidor. Não há limites para o tamanho do
arquivo de log ou para o número de arquivos de log criados.
Para registro em log de rotina em um aplicativo ASP.NET Core, use uma biblioteca de registro em log que limita o
tamanho do arquivo de log e realiza a rotação de logs. Para obter mais informações, veja provedores de log de
terceiros.
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout"
hostingModel="InProcess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>
</aspNetCore>
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>
</aspNetCore>
Depuração remota
Veja Depuração remota do ASP.NET Core em um computador de IIS remoto no Visual Studio 2017 na
documentação do Visual Studio.
Informações do aplicativo
O Application Insights fornece telemetria de aplicativos hospedados pelo IIS, incluindo recursos de relatório e
de registro de erros em log. O Application Insights só pode relatar erros ocorridos depois que o aplicativo é
iniciado quando os recursos de registro em log do aplicativo se tornam disponíveis. Para obter mais
informações, veja Application Insights para ASP.NET Core.
Orientações adicionais
Algumas vezes um aplicativo em funcionamento falha imediatamente após atualizar o SDK do .NET Core nas
versões de pacote ou de computador de desenvolvimento no aplicativo. Em alguns casos, pacotes incoerentes
podem interromper um aplicativo ao executar atualizações principais. A maioria desses problemas pode ser
corrigida seguindo estas instruções:
1. Exclua as pastas bin e obj.
2. Limpe os caches de pacote em %UserProfile%\.nuget\packages e %LocalAppData%\Nuget\v3 -cache.
3. Restaure e recompile o projeto.
4. Confirme que a implantação anterior no servidor foi completamente excluída antes de reimplantar o
aplicativo.
TIP
Uma maneira prática de se limpar caches do pacote é executar dotnet nuget locals all --clear de um prompt de
comando.
Também é possível limpar os caches de pacote usando a ferramenta nuget.exe e executando o comando
nuget locals all -clear . nuget.exe não é uma instalação fornecida com o sistema operacional Windows Desktop e
devem ser obtidos separadamente do site do NuGet.
Recursos adicionais
Solucionar problemas de projetos do ASP.NET Core
Tratar erros 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
Módulo do ASP.NET Core
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Referência de erros comuns para o Serviço de
Aplicativo do Azure e o IIS com o ASP.NET Core
21/01/2019 • 27 minutes to read • Edit Online
IMPORTANT
Versões prévias do ASP.NET Core com o Serviço de Aplicativo do Azure
Versões prévias do ASP.NET Core não são implantadas para o Serviço de Aplicativo do Azure por padrão. Para hospedar
um aplicativo que usa uma versão prévia do ASP.NET Core, veja Implantar versão prévia do ASP.NET Core para o Serviço
de Aplicativo do Azure.
Solução de problemas:
Para uma implantação dependente da estrutura x86 ( <PlatformTarget>x86</PlatformTarget> ), habilite o pool de
aplicativos de IIS para aplicativos de 32 bits. No Gerenciador do IIS, abra as Configurações Avançadas do
pool de aplicativos e defina Habilitar Aplicativos de 32 Bits como Verdadeiro.
WARNING
A hospedagem em uma configuração de proxy reverso exige a filtragem de host.
host.Run();
}
Opções do Kestrel
O servidor Web do Kestrel tem opções de configuração de restrição especialmente úteis em
implantações para a Internet.
Defina restrições na propriedade Limites da classe KestrelServerOptions. A propriedade
Limits contém uma instância da classe KestrelServerLimits.
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 .
[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()
Aqui está um exemplo que mostra como configurar a restrição para o aplicativo em cada
solicitação:
var minRequestRateFeature =
context.Features.Get<IHttpMinRequestBodyDataRateFeature>();
var minResponseRateFeature =
context.Features.Get<IHttpMinResponseDataRateFeature>();
if (minRequestRateFeature != null)
{
minRequestRateFeature.MinDataRate = new MinDataRate(
bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
}
if (minResponseRateFeature != null)
{
minResponseRateFeature.MinDataRate = new MinDataRate(
bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
}
Uma exceção será gerada se você tentar configurar o limite em uma solicitação depois que o
aplicativo começar a ler a 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
MinRequestBodyDataRate
MinResponseDataRate
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 de 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:
.ConfigureKestrel((context, 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");
});
});
var minRequestRateFeature =
context.Features.Get<IHttpMinRequestBodyDataRateFeature>();
var minResponseRateFeature =
context.Features.Get<IHttpMinResponseDataRateFeature>();
if (minRequestRateFeature != null)
{
minRequestRateFeature.MinDataRate = new MinDataRate(
bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
}
if (minResponseRateFeature != null)
{
minResponseRateFeature.MinDataRate = new MinDataRate(
bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
}
Configure (IConfiguration)
Cria um carregador de configuração para configurar o Kestrel que usa uma IConfiguration
como entrada. A configuração precisa estar no escopo da seção de configuração do Kestrel.
ListenOptions.UseHttps
Configure o Kestrel para usar HTTPS.
Extensões ListenOptions.UseHttps :
UseHttps – Configure o Kestrel para usar HTTPS com o certificado padrão. Gera uma
exceção quando não há nenhum certificado padrão configurado.
UseHttps(string fileName)
UseHttps(string fileName, string password)
UseHttps(string fileName, string password, Action<HttpsConnectionAdapterOptions>
configureOptions)
UseHttps(StoreName storeName, string subject)
UseHttps(StoreName storeName, string subject, bool allowInvalid)
UseHttps(StoreName storeName, string subject, bool allowInvalid, StoreLocation location)
UseHttps(StoreName storeName, string subject, bool allowInvalid, StoreLocation location,
Action<HttpsConnectionAdapterOptions> configureOptions)
UseHttps(X509Certificate2 serverCertificate)
UseHttps(X509Certificate2 serverCertificate, Action<HttpsConnectionAdapterOptions>
configureOptions)
UseHttps(Action<HttpsConnectionAdapterOptions> configureOptions)
Parâmetros de ListenOptions.UseHttps :
filename é o caminho e o nome do arquivo de um arquivo de certificado, relativo ao
diretório que contém os arquivos de conteúdo do aplicativo.
password é a senha necessária para acessar os dados do certificado X.509 .
configureOptions é uma Action para configurar as HttpsConnectionAdapterOptions .
Retorna o ListenOptions .
storeName é o repositório de certificados do qual o certificado deve ser carregado.
subject é o nome da entidade do certificado.
allowInvalid indica se certificados inválidos devem ser considerados, como os
certificados autoassinados.
location é o local do repositório do qual o certificado deve ser carregado.
serverCertificate é o certificado X.509.
"HttpsInlineCertFile": {
"Url": "https://localhost:5001",
"Certificate": {
"Path": "<path to .pfx file>",
"Password": "<certificate password>"
}
},
"HttpsInlineCertStore": {
"Url": "https://localhost:5002",
"Certificate": {
"Subject": "<subject; required>",
"Store": "<certificate store; defaults to My>",
"Location": "<location; defaults to CurrentUser>",
"AllowInvalid": "<true or false; defaults to false>"
}
},
"HttpsDefaultCert": {
"Url": "https://localhost:5003"
},
"Https": {
"Url": "https://*:5004",
"Certificate": {
"Path": "<path to .pfx file>",
"Password": "<certificate password>"
}
}
},
"Certificates": {
"Default": {
"Path": "<path to .pfx file>",
"Password": "<certificate password>"
}
}
}
}
"Default": {
"Subject": "<subject; required>",
"Store": "<cert store; defaults to My>",
"Location": "<location; defaults to CurrentUser>",
"AllowInvalid": "<true or false; defaults to false>"
}
Observações do esquema:
Os nomes de pontos de extremidade diferenciam maiúsculas de minúsculas. Por
exemplo, HTTPS e Https são válidos.
O parâmetro Url é necessário para cada ponto de extremidade. O formato desse
parâmetro é o mesmo que o do parâmetro de configuração de Urls de nível superior,
exceto que ele é limitado a um único valor.
Esses pontos de extremidade substituem aqueles definidos na configuração de Urls
de nível superior em vez de serem adicionados a eles. Os pontos de extremidade
definidos no código por meio de Listen são acumulados com os pontos de
extremidade definidos na seção de configuração.
A seção Certificate é opcional. Se a seção Certificate não for especificada, os
padrões definidos nos cenários anteriores serão usados. Se não houver nenhum
padrão disponível, o servidor gerará uma exceção e não poderá ser iniciado.
A seção Certificate é compatível com os certificados de Caminho–Senha e
Entidade–Repositório.
Qualquer número de pontos de extremidade pode ser definido dessa forma, contanto
que eles não causem conflitos de porta.
options.Configure(context.Configuration.GetSection("Kestrel")) retorna um
KestrelConfigurationLoadercom um método .Endpoint(string name, options => { })
que pode ser usado para complementar as definições de um ponto de extremidade
configurado:
options.Configure(context.Configuration.GetSection("Kestrel"))
.Endpoint("HTTPS", opt =>
{
opt.HttpsOptions.SslProtocols = SslProtocols.Tls12;
});
options.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls12;
});
return exampleCert;
};
});
});
});
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel((context, options) =>
{
options.ListenAnyIP(5005, listenOptions =>
{
listenOptions.UseHttps(httpsOptions =>
{
var localhostCert = CertificateLoader.LoadFromStoreCert(
"localhost", "My", StoreLocation.CurrentUser,
allowInvalid: true);
var exampleCert = CertificateLoader.LoadFromStoreCert(
"example.com", "My", StoreLocation.CurrentUser,
allowInvalid: true);
var subExampleCert = CertificateLoader.LoadFromStoreCert(
"sub.example.com", "My", StoreLocation.CurrentUser,
allowInvalid: true);
var certs = new Dictionary<string, X509Certificate2>(
StringComparer.OrdinalIgnoreCase);
certs["localhost"] = localhostCert;
certs["example.com"] = exampleCert;
certs["sub.example.com"] = subExampleCert;
return exampleCert;
};
});
});
})
.Build();
Porta 0
Quando o número da porta 0 for especificado, o Kestrel se associará dinamicamente a uma
porta disponível. O exemplo a seguir mostra como determinar a qual porta o Kestrel
realmente se associou no tempo de execução:
app.UseStaticFiles();
if (serverAddressesFeature != null)
{
await context.Response
.WriteAsync("<p>Listening on the following addresses: " +
string.Join(", ", serverAddressesFeature.Addresses) +
"</p>");
}
Quando o aplicativo é executado, a saída da janela do console indica a porta dinâmica na qual
o aplicativo pode ser acessado:
Limitações
Configure pontos de extremidade com as seguintes abordagens:
UseUrls
O argumento de linha de comando --urls
A chave de configuração do host urls
A variável de ambiente ASPNETCORE_URLS
Esses métodos são úteis para fazer com que o código funcione com servidores que não sejam
o Kestrel. No entanto, esteja ciente das seguintes limitações:
O protocolo HTTPS não pode ser usado com essas abordagens, a menos que um
certificado padrão seja fornecido na configuração do ponto de extremidade HTTPS (por
exemplo, usando a configuração KestrelServerOptions ou um arquivo de configuração,
como já foi mostrado neste tópico).
Quando ambas as abordagens, Listen e UseUrls , são usadas ao mesmo tempo, os
pontos de extremidade de Listen substituem os pontos de extremidade de UseUrls .
Configuração de ponto de extremidade do IIS
Ao usar o IIS, as associações de URL para IIS substituem as associações definidas por Listen
ou UseUrls . Para obter mais informações, confira o tópico Módulo do ASP.NET Core.
ListenOptions.Protocols
A propriedade Protocols estabelece os protocolos HTTP ( HttpProtocols ) habilitados em um
ponto de extremidade de conexão ou para o servidor. Atribua um valor à propriedade
Protocols com base na enumeração HttpProtocols .
return Task.FromResult<IAdaptedConnection>(new
AdaptedConnection(context.ConnectionStream));
}
{
"Kestrel": {
"EndPointDefaults": {
"Protocols": "Http1AndHttp2"
}
}
}
Configuração de transporte
Com a liberação do ASP.NET Core 2.1, o transporte padrão do Kestrel deixa de ser baseado
no Libuv, baseando-se agora em soquetes gerenciados. Essa é uma alteração da falha para
aplicativos ASP.NET Core 2.0 que atualizam para o 2.1 que chamam
WebHostBuilderLibuvExtensions.UseLibuv e dependem de um dos seguintes pacotes:
Microsoft.AspNetCore.Server.Kestrel (referência direta de pacote)
Microsoft.AspNetCore.App
Para projetos do ASP.NET Core 2.1 ou posteriores que usam o metapacote
Microsoft.AspNetCore.App e exigem o uso de Libuv:
Adicione uma dependência para o pacote
Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv ao arquivo de projeto do
aplicativo:
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv"
Version="<LATEST_VERSION>" />
Chame WebHostBuilderLibuvExtensions.UseLibuv:
Prefixos de URL
Ao usar UseUrls , o argumento de linha de comando --urls , a chave de configuração de host
urls ou a variável de ambiente ASPNETCORE_URLS , os prefixos de URL podem estar em um
dos formatos a seguir.
Somente os prefixos de URL HTTP são válidos. O Kestrel não é compatível com HTTPS ao
configurar associações de URL que usam 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/
http://contoso.com:80/
http://*:80/
Os nomes de host * e + não são especiais. Tudo o que não é reconhecido como um
endereço IP ou um localhost válido é associado a todos os IPs IPv6 e IPv4. Para
associar nomes de host diferentes a diferentes aplicativos ASP.NET Core na mesma
porta, use o HTTP.sys ou um servidor proxy reverso, como o IIS, o Nginx ou o Apache.
WARNING
A hospedagem em uma configuração de proxy reverso exige a filtragem de host.
http://localhost:5000/
http://127.0.0.1:5000/
http://[::1]:5000/
Filtragem de host
Embora o Kestrel permita a configuração com base em prefixos como
http://example.com:5000 , ele geralmente ignora o nome do host. O host localhost é um caso
especial usado para a associação a endereços de loopback. Todo host que não for um
endereço IP explícito será associado a todos os endereços IP públicos. Cabeçalhos Host não
são validados.
Como uma solução alternativa, use o Middleware de Filtragem de Host. Middleware de
Filtragem de Host é fornecido pelo pacote Microsoft.AspNetCore.HostFiltering, que está
incluído no metapacote Microsoft.AspNetCore.App (ASP.NET Core 2.1 ou posterior). O
middleware é adicionado por CreateDefaultBuilder, que chama AddHostFiltering:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
Middleware de Filtragem de Host está desabilitado por padrão. Para habilitar o middleware,
defina uma chave AllowedHosts em appSettings. JSON/appsettings.
<EnvironmentName>.json. O valor dessa chave é uma lista separada por ponto e vírgula de
nomes de host sem números de porta:
appsettings.json:
{
"AllowedHosts": "example.com;localhost"
}
NOTE
Middleware de Cabeçalhos Encaminhados também tem uma opção
ForwardedHeadersOptions.AllowedHosts. Middleware de Cabeçalhos Encaminhados e Middleware de
filtragem de Host têm funcionalidades semelhantes para cenários diferentes. A definição de
AllowedHosts com Middleware de Cabeçalhos Encaminhados é apropriada quando o cabeçalho
Host não é preservado ao encaminhar solicitações com um servidor proxy reverso ou um
balanceador de carga. A definição de AllowedHosts com Middleware de Filtragem de Host é
apropriada quando o Kestrel é usado como um servidor de borda voltado ao público ou quando o
cabeçalho Host é encaminhado diretamente.
Para obter mais informações sobre o Middleware de Cabeçalhos Encaminhados, confira Configure o
ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga.
Recursos adicionais
Solucionar problemas de projetos do ASP.NET Core
Impor HTTPS no ASP.NET Core
Configure o ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga
Código-fonte do kestrel
RFC 7230: sintaxe e roteamento da mensagem (Seção 5.4: host)
Implementação do servidor Web HTTP.sys no
ASP.NET Core
16/01/2019 • 19 minutes to read • Edit Online
IMPORTANT
O HTTP.sys não é compatível com o Módulo do ASP.NET Core e não pode ser usado com IIS ou IIS Express.
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.
Compatibilidade com HTTP/2
O HTTP/2 estará habilitado para aplicativos ASP.NET Core se os seguintes requisitos básicos forem
atendidos:
Windows Server 2016/Windows 10 ou posterior
Conexão ALPN (Negociação de protocolo de camada de aplicativo)
Conexão TLS 1.2 ou posterior
Se uma conexão HTTP/2 for estabelecida, HttpRequest.Protocol relatará HTTP/2 .
Se uma conexão HTTP/2 for estabelecida, HttpRequest.Protocol relatará HTTP/1.1 .
O HTTP/2 está habilitado por padrão. Se uma conexão HTTP/2 não tiver sido estabelecida, a conexão
retornará para HTTP/1.1. Em uma versão futura do Windows, os sinalizadores de configuração de
HTTP/2 estarão disponíveis e contarão com a capacidade de desabilitar o HTTP/2 com 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:
var serverAddressesFeature =
app.ServerFeatures.Get<IServerAddressesFeature>();
var addresses = string.Join(", ", serverAddressesFeature?.Addresses);
logger.LogInformation($"Addresses: {addresses}");
await next.Invoke();
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
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 obter mais informações, consulte Host
da Web do ASP.NET Core.
O HTTP.sys usa os formatos de cadeia de caracteres UrlPrefix da API do Servidor HTTP.
WARNING
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 criam vulnerabilidades de segurança no aplicativo. Isso se aplica
a curingas fortes e fracos. Use nomes de host explícitos ou endereços IP em vez de curingas. Associações
de curinga de subdomínio (por exemplo, *.mysub.com ) não serão um risco à segurança se você
controlar todo o domínio pai (ao contrário de *.com , o qual é vulnerável). Para saber mais, confira RFC
7230: Seção 5.4: Host.
<URL> – A URL (Uniform Resource Locator) totalmente qualificada. Não use uma associação
de curinga. Use um nome de host válido ou o endereço IP local. A URL deve incluir uma barra
à direita.
<USER> – Especifica o nome de usuário ou do grupo de usuários.
No exemplo a seguir, o endereço IP local do servidor é 10.0.0.4 :
Quando uma URL é registrada, a ferramenta responde com URL reservation successfully added .
Para excluir uma URL registrada, use o comando delete urlacl :
<IP> – Especifica o endereço IP local para a associação. Não use uma associação de curinga.
Use um endereço IP válido.
<PORT> – Especifica a porta da associação.
<THUMBPRINT> – A impressão digital do certificado X.509.
<GUID> – Um GUID gerado pelo desenvolvedor para representar o aplicativo para fins
informativos.
Para fins de referência, armazene o GUID no aplicativo como uma marca de pacote:
No Visual Studio:
Abra as propriedades do projeto do aplicativo, clicando com o botão direito do mouse
no aplicativo no Gerenciador de Soluções e selecionando Propriedades.
Selecione a guia Pacote.
Insira o GUID que você criou no campo Marcas.
Quando não estiver usando o Visual Studio:
Abra o arquivo de projeto do aplicativo.
Adicione uma propriedade <PackageTags> a um <PropertyGroup> novo ou existente
com o GUID que você criou:
<PropertyGroup>
<PackageTags>9412ee86-c21b-4eb8-bd89-f650fbf44931</PackageTags>
</PropertyGroup>
No exemplo a seguir:
O endereço IP local do servidor é 10.0.0.4 .
Um gerador GUID aleatório online fornece o valor appid .
Recursos adicionais
Habilitar a autenticação do Windows com HTTP.sys
API do servidor HTTP
Repositório aspnet/HttpSysServer do GitHub (código-fonte)
Host da Web e Host Genérico no ASP.NET Core
Solucionar problemas de projetos do ASP.NET Core
Hospedar o ASP.NET Core em um serviço Windows
30/01/2019 • 19 minutes to read • Edit Online
Tipo de implantação
Você pode criar uma implantação de Serviço Windows dependente de estrutura ou autocontida. Para saber mais e
obter conselhos sobre cenários de implantação, consulte Implantação de aplicativos .NET Core.
Implantação dependente de estrutura
A FDD (Implantação Dependente de Estrutura) se baseia na presença de uma versão compartilhada em todo o
sistema do .NET Core no sistema de destino. Quando o cenário FDD é usado com um aplicativo de Serviço
Windows do ASP.NET Core, o SDK produz um executável (*.exe), chamado de executável dependente de
estrutura.
Implantação autocontida
A SCD (Implantação Autocontida) não se baseia na presença de componentes compartilhados no sistema de
destino. O tempo de execução e as dependências do aplicativo são implantados com o aplicativo para o sistema de
hospedagem.
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>
Adicione a propriedade <UseAppHost> definida como true . Essa propriedade fornece o serviço com um caminho
de ativação (um arquivo executável .exe) para FDD.
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
<UseAppHost>true</UseAppHost>
<SelfContained>false</SelfContained>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
Directory.SetCurrentDirectory(pathToContentRoot);
}
if (isService)
{
// To run the app without the CustomWebHostService change the
// next line to host.RunAsService();
host.RunAsCustomService();
}
else
{
host.Run();
}
}
Publique o aplicativo
Publique o aplicativo usando dotnet publish, um perfil de publicação do Visual Studio ou o Visual Studio Code. Ao
usar o Visual Studio, selecione o FolderProfile e configure o Local de destino antes de selecionar o botão
Publicar.
Para publicar o aplicativo de exemplo usando as ferramentas da CLI (Interface de Linha de Comando), execute o
comando dotnet publish em um prompt de comando da pasta do projeto, passando a configuração de uma versão
para a opção -c|--configuration. Use a opção -o|--output com um caminho para publicar em uma pasta fora do
aplicativo.
Publicar uma FDD (Implantação Dependente de Estrutura)
No exemplo a seguir, o aplicativo é publicado na pasta c:\svc:
Para o aplicativo de exemplo, crie uma conta de usuário com o nome ServiceUser e uma senha. No comando a
seguir, substitua {PASSWORD} por uma senha forte.
Se você precisar adicionar o usuário a um grupo, use o comando net localgroup , onde {GROUP} é o nome do
grupo:
Para o aplicativo de exemplo publicado na pasta c:\svc e a conta ServiceUser com permissões de
gravação/leitura/execução, use o seguinte comando:
sc create {SERVICE NAME} binPath= "{PATH}" obj= "{DOMAIN}\{USER ACCOUNT}" password= "{PASSWORD}"
WARNING
Não omita o parâmetro obj . O valor padrão para obj é a conta LocalSystem. Executar um serviço na conta
LocalSystem apresenta um risco de segurança significativo. Sempre execute um serviço com uma conta de usuário com
privilégios restritos.
IMPORTANT
Certifique-se de que os espaços entre os sinais de igual e os valores dos parâmetros estão presentes.
Iniciar o serviço
Inicie o serviço com o comando sc start {SERVICE NAME} .
Para iniciar o serviço de aplicativo de exemplo, use o seguinte comando:
sc start MyService
sc query MyService
sc stop MyService
Excluir o serviço
Após um pequeno atraso para interromper um serviço, desinstale o serviço com o comando
sc delete {SERVICE NAME} .
sc query MyService
Quando o serviço de aplicativo de exemplo estiver no estado STOPPED , use o seguinte comando para desinstalar o
serviço de aplicativo de exemplo:
sc delete MyService
2. Crie um método de extensão para IWebHost que passe o CustomWebHostService para Run:
host.RunAsCustomService();
Para ver o local do RunAsService no Program.Main , consulte o exemplo de código mostrado na seção
Converter um projeto em um Serviço Windows.
Configurar o HTTPS
Para configurar o serviço com um ponto de extremidade seguro:
1. Crie um certificado X.509 para o sistema de hospedagem usando os mecanismos de implantação e
aquisição do certificado da sua plataforma.
2. Especifique uma Configuração do ponto de extremidade HTTPS do servidor Kestrel para usar o certificado.
Não há suporte para uso do certificado de desenvolvimento HTTPS do ASP.NET Core para proteger um ponto de
extremidade de serviço.
CreateWebHostBuilder(args)
.Build()
.RunAsService();
Recursos adicionais
Configuração do ponto de extremidade Kestrel (inclui a configuração HTTPS e suporte à SNI)
Host da Web do ASP.NET Core
Solucionar problemas de projetos do ASP.NET Core
Host ASP.NET Core no Linux com Nginx
16/01/2019 • 24 minutes to read • Edit Online
NOTE
Para Ubuntu 14.04, o supervisord é recomendado como uma solução para monitorar o processo do Kestrel. O systemd
não está disponível no Ubuntu 14.04. Para obter instruções Ubuntu 14.04, veja a versão anterior deste tópico.
Este guia:
Coloca um aplicativo ASP.NET Core existente em um servidor proxy reverso.
Configura o servidor proxy reverso para encaminhar solicitações ao servidor Web do Kestrel.
Assegura que o aplicativo Web seja 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. Instale o tempo de execução do .NET Core no servidor.
a. Acesse a página Todos os Downloads do .NET Core.
b. Selecione o tempo de execução não de versão prévia mais recente da lista em Tempo de Execução.
c. Selecione e siga as instruções para Ubuntu que correspondem à versão Ubuntu do servidor.
3. Um aplicativo ASP.NET Core existente.
O aplicativo também poderá ser publicado como uma implantação autossuficiente se você preferir não manter
o tempo de execução do .NET Core no servidor.
Copie o aplicativo ASP.NET Core para o servidor usando uma ferramenta que se integre ao fluxo de trabalho
da organização (por exemplo, SCP, SFTP ). É comum para localizar os aplicativos Web no diretório var (por
exemplo, var/www/helloapp).
NOTE
Em um cenário de implantação de produção, um fluxo de trabalho de integração contínua faz o trabalho de publicar o
aplicativo e copiar os ativos para o servidor.
Teste o aplicativo:
1. Na linha de comando, execute o aplicativo: dotnet <app_assembly>.dll .
2. Em um navegador, vá para http://<serveraddress>:<port> para verificar se o aplicativo funciona no Linux
localmente.
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseAuthentication();
app.UseIdentity();
app.UseFacebookAuthentication(new FacebookOptions()
{
AppId = Configuration["Authentication:Facebook:AppId"],
AppSecret = Configuration["Authentication:Facebook:AppSecret"]
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});
Para obter mais informações, consulte Configure o ASP.NET Core para trabalhar com servidores proxy e
balanceadores de carga.
Instalar o Nginx
Use apt-get para instalar o Nginx. O instalador cria um script de inicialização systemd que executa o Nginx
como daemon na inicialização do sistema. Siga as instruções de instalação do Ubuntu no Nginx: pacotes
Debian/Ubuntu oficiais.
NOTE
Se módulos Nginx opcionais forem exigidos, poderá haver necessidade de criar o Nginx da origem.
Já que Nginx foi instalado pela primeira vez, inicie-o explicitamente executando:
Quando nenhum server_name corresponde, o Nginx usa o servidor padrão. Se nenhum servidor padrão é
definido, o primeiro servidor no arquivo de configuração é o servidor padrão. Como prática recomendada,
adicione um servidor padrão específico que retorna um código de status 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 o servidor padrão e o arquivo de configuração anterior, 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 correspondam a esses hosts não
serão encaminhadas para o Kestrel. O Nginx encaminha as solicitações correspondentes para o Kestrel em
http://localhost:5000 . Veja Como o nginx processa uma solicitação para obter mais informações. Para alterar
o IP/porta do Kestrel, veja Kestrel: configuração do ponto de extremidade.
WARNING
Falha ao especificar uma diretiva server_name expõe seu aplicativo para vulnerabilidades de segurança. Associações de
curinga de subdomínio (por exemplo, *.example.com ) não oferecerão 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.
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-sucedido, você poderá forçar o Nginx a
acompanhar as alterações executando sudo nginx -s reload .
Para executar o aplicativo diretamente no servidor:
1. Navegue até o diretório do aplicativo.
2. Execute o aplicativo: dotnet <app_assembly.dll> , em que app_assembly.dll é o nome do arquivo do
assembly do aplicativo.
Se o aplicativo for executado no servidor, mas não responder pela Internet, verifique o firewall do servidor e
confirme que a porta 80 está aberta. Se você estiver usando uma VM do Azure Ubuntu, adicione uma regra
NSG (Grupo de Segurança de Rede) que permite tráfego de entrada na porta 80. Não é necessário habilitar
uma regra de saída da porta 80, uma vez que o tráfego de saída é concedido automaticamente quando a regra
de entrada é habilitada.
Quando terminar de testar o aplicativo, encerre o aplicativo com Ctrl+C no prompt de comando.
Monitorar o aplicativo
O servidor agora está configurado para encaminhar solicitações feitas a http://<serveraddress>:80 ao
aplicativo ASP.NET Core em execução no Kestrel em http://127.0.0.1:5000 . No entanto, o Nginx não está
configurado para gerenciar o processo do Kestrel. É possível usar o systemd 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:
[Unit]
Description=Example .NET Web API App running on Ubuntu
[Service]
WorkingDirectory=/var/www/helloapp
ExecStart=/usr/bin/dotnet /var/www/helloapp/helloapp.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
Se o usuário www -data não é usado pela configuração, o usuário definido aqui deve ser criado primeiro e a
propriedade adequada dos arquivos deve ser concedida a ele.
Use TimeoutStopSec para configurar a duração do tempo de espera para o aplicativo desligar depois de receber
o sinal de interrupção inicial. Se o aplicativo não desligar nesse período, o SIGKILL será emitido para encerrá-
lo. Forneça o valor como segundos sem unidade (por exemplo, 150 ), um valor de duração (por exemplo,
2min 30s ) ou infinity para desabilitar o tempo limite. TimeoutStopSec é revertido para o valor padrão de
DefaultTimeoutStopSec no arquivo de configuração do gerenciador (systemd -system.conf, system.conf.d,
systemd -user.conf e user.conf.d). O tempo limite padrão para a maioria das distribuições é de 90 segundos.
systemd-escape "<value-to-escape>"
Salve o arquivo e habilite o serviço.
Com o proxy reverso configurado e o Kestrel gerenciado por meio de systemd, o aplicativo Web está
totalmente configurado e pode ser acessado em um navegador no computador local em http://localhost . Ele
também é acessível por meio de um computador remoto, bloqueando qualquer firewall que o possa estar
bloqueando. Inspecionando os cabeçalhos de resposta, o cabeçalho Server mostra o aplicativo ASP.NET Core
sendo servido pelo 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
Exibir logs
Já que o aplicativo Web usando Kestrel é gerenciado usando systemd , todos os eventos e os processos são
registrados em 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-helloapp.service , use o
seguinte comando:
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.
Proteção de dados
A pilha de proteção de dados do ASP.NET Core é usada por vários middlewares do ASP.NET Core, incluindo o
middleware de autenticação (por exemplo, o middleware de cookie) e as proteções de CSRF (solicitação
intersite forjada). 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 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 de modo que ela mantenha e criptografe o token de autenticação, consulte:
Provedores de armazenamento de chaves no ASP.NET Core
Chave de criptografia em repouso no ASP.NET Core
WARNING
Não aumente os valores padrão de buffers de proxy a menos que necessário. Aumentar esses valores aumenta o risco de
estouro de buffer (estouro) e ataques de DoS (negação de serviço) por usuários mal-intencionados.
Proteger o aplicativo
Habilitar AppArmor
O LSM (Módulos de Segurança do Linux) é uma estrutura que é parte do kernel do Linux desde o Linux 2.6. 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.
Configurar 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.
WARNING
Um firewall impedirá o acesso a todo o sistema se não ele estiver configurado corretamente. Se houver falha ao
especificar a porta SSH correta, você será bloqueado do sistema se estiver usando o SSH para se conectar a ele. A porta
padrão é a 22. Para obter mais informações, consulte a introdução ao ufw e o manual.
Proteger o Nginx
Alterar o nome da resposta do Nginx
Edite src/http/ngx_http_header_filter_module.c:
Configurar opções
Configure o servidor com os módulos adicionais necessários. Considere usar um firewall de aplicativo Web
como ModSecurity para fortalecer o aplicativo.
Configuração de HTTPS
Configure o servidor para escutar tráfego HTTPS na porta 443 especificando um certificado válido
emitido por uma AC (autoridade de certificação) confiável.
Aprimore a segurança, empregando algumas das práticas descritas no arquivo /etc/nginx/nginx.conf a
seguir. Exemplos incluem a escolha de uma criptografia mais forte e o redirecionamento de todo o
tráfego por meio de HTTP para HTTPS.
A adição de um cabeçalho HTTP Strict-Transport-Security (HSTS ) garante que todas as próximas
solicitações feitas pelo cliente sejam por HTTPS.
Não adicione o cabeçalho HSTS ou escolha um max-age apropriado, se quiser desabilitar o HTTPS
futuramente.
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-Content-Type-Options "nosniff"; e salve o arquivo, depois reinicie o Nginx.
Recursos adicionais
Pré-requisitos para o .NET Core no Linux
Nginx: versões binárias: pacotes Debian/Ubuntu oficiais
Solucionar problemas de projetos do ASP.NET Core
Configure o ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga
NGINX: usando o cabeçalho Encaminhado
Hospedar o ASP.NET Core no Linux com o Apache
16/01/2019 • 22 minutes to read • Edit Online
Pré-requisitos
Servidor que executa o CentOS 7 com uma conta de usuário padrão com privilégio sudo.
Instale o tempo de execução do .NET Core no servidor.
1. Acesse a página Todos os Downloads do .NET Core.
2. Selecione o tempo de execução não de versão prévia mais recente da lista em Tempo de Execução.
3. Selecione e siga as instruções para CentOS/Oracle.
Um aplicativo ASP.NET Core existente.
O aplicativo também poderá ser publicado como uma implantação autossuficiente se você preferir não manter
o tempo de execução do .NET Core no servidor.
Copie o aplicativo ASP.NET Core para o servidor usando uma ferramenta que se integre ao fluxo de trabalho
da organização (por exemplo, SCP, SFTP ). É comum para localizar os aplicativos Web no diretório var (por
exemplo, var/www/helloapp).
NOTE
Em um cenário de implantação de produção, um fluxo de trabalho de integração contínua faz o trabalho de publicar o
aplicativo e copiar os ativos para o servidor.
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseAuthentication();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseIdentity();
app.UseFacebookAuthentication(new FacebookOptions()
{
AppId = Configuration["Authentication:Facebook:AppId"],
AppSecret = Configuration["Authentication:Facebook:AppSecret"]
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});
Para obter mais informações, consulte Configure o ASP.NET Core para trabalhar com servidores proxy e
balanceadores de carga.
Instalar o Apache
Atualize os pacotes CentOS para as respectivas versões estáveis mais recentes:
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!
NOTE
Neste exemplo, o resultado reflete httpd.86_64, pois a versão do CentOS 7 é de 64 bits. Para verificar o local em que o
Apache está instalado, execute whereis httpd em um prompt de comando.
Configurar o Apache
Os arquivos de configuração do Apache estão localizados no diretório /etc/httpd/conf.d/ . Qualquer arquivo
com a extensão .conf é processado em ordem alfabética, além dos arquivos de configuração do módulo em
/etc/httpd/conf.modules.d/ , que contém todos os arquivos de configuração necessários para carregar os
módulos.
Crie um arquivo de configuração chamado helloapp.conf para o aplicativo:
<VirtualHost *:*>
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
</VirtualHost>
<VirtualHost *:80>
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
ServerName www.example.com
ServerAlias *.example.com
ErrorLog ${APACHE_LOG_DIR}helloapp-error.log
CustomLog ${APACHE_LOG_DIR}helloapp-access.log common
</VirtualHost>
O bloco VirtualHost pode aparecer várias vezes, em um ou mais arquivos em um servidor. No arquivo de
configuração anterior, o Apache aceita tráfego público na porta 80. O domínio www.example.com está sendo
atendido e o alias *.example.com é resolvido para o mesmo site. Veja Suporte a host virtual baseado em nome
para obter mais informações. As solicitações passadas por proxy na raiz para a porta 5000 do servidor em
127.0.0.1. Para a comunicação bidirecional, ProxyPass e ProxyPassReverse são necessários. Para alterar o
IP/porta do Kestrel, veja Kestrel: configuração do ponto de extremidade.
WARNING
Falha ao especificar uma diretiva ServerName no bloco VirtualHost expõe seu aplicativo para vulnerabilidades de
segurança. Associações de curinga de subdomínio (por exemplo, *.example.com ) não oferecerão 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.
O registro em log pode ser configurado por VirtualHost usando diretivas ErrorLog e CustomLog . ErrorLog é
o local em que o servidor registrará em log os erros, enquanto CustomLog define o nome de arquivo e o
formato do arquivo de log. Nesse caso, esse é o local em que as informações de solicitação são registradas em
log. Há uma linha para cada solicitação.
Salve o arquivo e teste a configuração. Se tudo passar, a resposta deverá ser Syntax [OK] .
Reinicie o Apache:
Monitorar o aplicativo
O Apache agora está configurado para encaminhar solicitações feitas a http://localhost:80 para o aplicativo
ASP.NET Core em execução no Kestrel em http://127.0.0.1:5000 . No entanto, o Apache não está configurado
para gerenciar o processo do 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/www/helloapp
ExecStart=/usr/local/bin/dotnet /var/www/helloapp/helloapp.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=apache
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target
Se o usuário apache não for usado pela configuração, o usuário precisará ser criado primeiro e a propriedade
adequada dos arquivos precisará ser concedida a ele.
Use TimeoutStopSec para configurar a duração do tempo de espera para o aplicativo desligar depois de receber
o sinal de interrupção inicial. Se o aplicativo não desligar nesse período, o SIGKILL será emitido para encerrá-
lo. Forneça o valor como segundos sem unidade (por exemplo, 150 ), um valor de duração (por exemplo,
2min 30s ) ou infinity para desabilitar o tempo limite. TimeoutStopSec é revertido para o valor padrão de
DefaultTimeoutStopSec no arquivo de configuração do gerenciador (systemd -system.conf, system.conf.d,
systemd -user.conf e user.conf.d). O tempo limite padrão para a maioria das distribuições é de 90 segundos.
Alguns valores (por exemplo, cadeias de conexão de SQL ) devem ser escapadas para que os provedores de
configuração leiam as variáveis de ambiente. Use o seguinte comando para gerar um valor corretamente com
caracteres de escape para uso no arquivo de configuração:
systemd-escape "<value-to-escape>"
Com o proxy reverso configurado e o Kestrel gerenciado por meio de systemd, o aplicativo Web está totalmente
configurado e pode ser acessado em um navegador no computador local em http://localhost . Inspecionando
os cabeçalhos de resposta, o cabeçalho Server indica que o aplicativo ASP.NET Core é servido pelo 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
Exibir logs
Já que o aplicativo Web usando Kestrel é gerenciado usando systemd, os eventos e os processos são
registrados em um diário centralizado. No entanto, esse diário contém todas as entradas para todos os serviços
e processos gerenciados por systemd. Para exibir os itens específicos de kestrel-helloapp.service , use o
seguinte comando:
Para filtragem de hora, especifique 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,
confira a página de manual para journalctl.
Proteção de dados
A pilha de proteção de dados do ASP.NET Core é usada por vários middlewares do ASP.NET Core, incluindo o
middleware de autenticação (por exemplo, o middleware de cookie) e as proteções de CSRF (solicitação
intersite forjada). 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 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 de modo que ela mantenha e criptografe o token de autenticação, consulte:
Provedores de armazenamento de chaves no ASP.NET Core
Chave de criptografia em repouso no ASP.NET Core
Proteger 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 deve ser instalado por padrão. yum pode ser
usado para instalar o pacote ou verificar se ele está instalado.
Configuração de HTTPS
Para configurar o Apache para HTTPS, o módulo mod_ssl é usado. Quando o módulo httpd foi instalado, o
módulo mod_ssl também foi instalado. Se ele não foi instalado, use yum para adicioná-lo à configuração.
Para impor o HTTPS, instale o módulo mod_rewrite para habilitar a regravação de URL:
Modifique o arquivo helloapp.conf para habilitar a regravação de URL e proteger a comunicação na porta 443:
<VirtualHost *:*>
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
</VirtualHost>
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [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/helloapp-error.log
CustomLog /var/log/httpd/helloapp-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>
NOTE
Este exemplo usa 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 o CSR é criado.
SSLCertificateChainFile deve ser o arquivo de certificado intermediário (se houver) 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, o uso de mod_proxy_balancer e a modificação do VirtualHost
permitiriam o gerenciamento de várias instâncias dos aplicativos Web protegidos pelo servidor proxy do
Apache.
No arquivo de configuração mostrado abaixo, uma instância adicional do helloapp é configurada para ser
executada na porta 5001. A seção Proxy é definida com uma configuração de balanceador com dois membros
para balancear carga de byrequests.
<VirtualHost *:*>
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
</VirtualHost>
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [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/helloapp-error.log
CustomLog /var/log/httpd/helloapp-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 no módulo httpd, a largura de banda de clientes pode ser limitada:
<IfModule mod_ratelimit.c>
<Location />
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 600
</Location>
</IfModule>
WARNING
Não aumente o valor padrão de LimitRequestFieldSize a menos que necessário. Aumentar esse valor aumenta o risco
de estouro de buffer (estouro) e ataques de DoS (negação de serviço) por usuários mal-intencionados.
Recursos adicionais
Pré-requisitos para o .NET Core no Linux
Solucionar problemas de projetos do ASP.NET Core
Configure o ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga
Hospedar o ASP.NET Core em contêineres do
Docker
21/06/2018 • 2 minutes 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
30/10/2018 • 19 minutes to read • Edit Online
O Visual Studio 2017 é compatível com a criação, depuração e execução de aplicativos do ASP.NET Core em
contêineres direcionados ao .Net Core. Contêineres do Windows e do Linux são compatíveis.
Exibir ou baixar código de exemplo (como baixar)
Pré-requisitos
Docker para Windows
O Visual Studio 2017 com a carga de trabalho Desenvolvimento de multiplataforma do .NET Core
Instalação e configuração
Para a instalação do Docker, primeiro examine as informações em Docker for Windows: What to know before you
install (Docker para Windows: o que saber antes de instalar). Em seguida, instale o Docker for Windows.
As Unidades Compartilhadas do Docker para Windows devem ser configuradas para dar suporte ao
mapeamento do volume e à depuração. Clique com o botão direito do mouse no ícone do Docker na Bandeja do
Sistema, selecione Configurações e selecione Unidades Compartilhadas. Selecione a unidade em que o Docker
armazena arquivos. Clique em Aplicar.
TIP
A versão 15.6 e as versões posteriores do Visual Studio 2017 exibem um aviso quando as Unidades Compartilhadas não
estão configuradas.
Adicionar um projeto a um contêiner do Docker
Para colocar um projeto do ASP.NET Core em contêineres, o projeto deve ser destinado ao .Net Core. Contêineres
do Linux e Windows são ambos compatíveis.
Ao adicionar o suporte do Docker a um projeto, escolha um contêiner do Windows ou Linux. O host do Docker
deve estar executando o mesmo tipo de contêiner. Para alterar o tipo de contêiner na instância do Docker em
execução, clique com o botão direito do mouse no ícone do Docker na Bandeja do Sistema e escolha Alternar
para contêineres do Windows... ou Alternar para contêineres do Linux....
Novo aplicativo
Ao criar um novo aplicativo com os modelos de projeto do Aplicativo Web ASP.NET Core, marque a caixa de
seleção Habilitar Suporte do Docker:
Se a estrutura de destino for o .NET Core, a lista suspensa SO permitirá a seleção de um tipo de contêiner.
Aplicativo existente
Para projetos do ASP.NET Core direcionados ao .NET Core, há duas opções para adicionar o suporte do Docker
por meio das ferramentas. Abra o projeto no Visual Studio e escolha uma das seguintes opções:
Selecione Suporte do Docker no menu Projeto.
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Adicionar >
Suporte do Docker.
As Ferramentas do Visual Studio para Docker não dão suporte à adição do Docker a um projeto ASP.NET Core
existente direcionado ao .NET Framework.
O Dockerfile precedente se baseia na imagem microsoft/dotnet. Essa imagem base inclui o tempo de execução do
ASP.NET Core e os pacotes NuGet. Os pacotes são compilados JIT (Just-In-Time) para melhorar o desempenho
de inicialização.
Quando a caixa de seleção Configurar para HTTPS do novo projeto estiver marcada, o Dockerfile exibirá duas
portas. Uma porta é usada para o tráfego HTTP; a outra porta é usada para o HTTPS. Se a caixa de seleção não
estiver marcada, uma única porta (80) será exposta para o tráfego HTTP.
O Dockerfile precedente se baseia na imagem microsoft/aspnetcore. Essa imagem base inclui os pacotes NuGet
do ASP.NET Core, que são compilados JIT (Just-In-Time) para melhorar o desempenho de inicialização.
O arquivo docker-compose.yml faz referência ao nome da imagem que é criada quando o projeto é executado:
version: '3.4'
services:
hellodockertools:
image: ${DOCKER_REGISTRY}hellodockertools
build:
context: .
dockerfile: HelloDockerTools/Dockerfile
Se você desejar um comportamento diferente com base na configuração de build (por exemplo, Depuração ou
Versão), adicione arquivos docker-compose específicos para a configuração. Os arquivos precisam ser nomeados
de acordo com a configuração de build (por exemplo, docker-compose.vs.debug.yml e docker-
compose.vs.release.yml) e colocado no mesmo local que o arquivo docker-compose-override.yml.
Usando os arquivos de substituição específicos da configuração, é possível especificar definições de configuração
diferentes (como variáveis de ambiente ou pontos de entrada) para as configurações de build de Depuração e
Versão.
Service Fabric
Além dos pré-requisitos básicos, a solução de orquestração do Service Fabric exige os seguintes pré-requisitos:
SDK do Microsoft Azure Service Fabric versão 2.6 ou posterior
Carga de trabalho de desenvolvimento do Azure no Visual Studio 2017
O Service Fabric não é compatível com a execução de contêineres do Linux no cluster de desenvolvimento local no
Windows. Se o projeto já estiver usando um contêiner do Linux, o Visual Studio pedirá para alternar para os
contêineres do Windows.
As Ferramentas do Visual Studio para Docker realizam as seguintes tarefas:
Adiciona um projeto <project_name>do***Aplicativo do Service Fabric* à solução.
Adiciona um Dockerfile e um arquivo .dockerignore ao projeto ASP.NET Core. Se um Dockerfile já existir
no projeto do ASP.NET Core, ele já terá sido renomeado para Dockerfile.original. Um novo Dockerfile,
semelhante ao seguinte, foi criado:
# See https://aka.ms/containerimagehelp for information on how to use Windows Server 1709 containers
with Service Fabric.
# FROM microsoft/aspnetcore:2.0-nanoserver-1709
FROM microsoft/aspnetcore:2.0-nanoserver-sac2016
ARG source
WORKDIR /app
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "HelloDockerTools.dll"]
<IsServiceFabricServiceProject>True</IsServiceFabricServiceProject>
Adiciona uma pasta PackageRoot ao projeto ASP.NET Core. A pasta inclui o manifesto do serviço e as
configurações para o novo serviço.
Para obter mais informações, consulte Implantar um aplicativo .NET em um contêiner do Windows no Azure
Service Fabric.
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:
A marcação 2.1 -aspnetcore-runtime da imagem do tempo de execução microsoft/dotnet foi adquirida (se ainda
não estiver no cache). A imagem instala os tempos de execução do ASP.NET Core e do .Net Core e bibliotecas
associadas. Ela é otimizada para executar aplicativos do ASP.NET Core em produção.
A variável de ambiente ASPNETCORE_ENVIRONMENT é definida como Development dentro do contêiner.
Duas portas atribuídas dinamicamente são expostas: uma para HTTP e outra para HTTPS. A porta atribuída ao
localhost 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 do aplicativo é marcada como dev. A imagem é baseada na marcação 2.1 -
aspnetcore-runtime da imagem base microsoft/dotnet. Execute o comando docker images na janela do PMC
(Console do Gerenciador de Pacotes). As imagens no computador são exibidas:
A imagem em tempo de execução microsoft/aspnetcore é adquirida (se ainda não está 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 do aplicativo é marcada como dev. A imagem é baseada na imagem base
microsoft/aspnetcore. Execute o comando docker images na janela do PMC (Console do Gerenciador de
Pacotes). As imagens no computador são exibidas:
NOTE
A imagem dev não inclui o conteúdo do aplicativo, pois as configurações de Depuração usam a montagem de volume para
fornecer uma experiência iterativa. 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 nos 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 é recompilado nem interrompido. Execute o comando docker ps no PMC. Observe que
o contêiner original ainda está em execução como há 10 minutos:
NOTE
O comando docker images retorna imagens intermediárias com nomes de repositório e marcas identificadas como
<none> (não listadas acima). Essas imagens sem nome são produzidas pelo Dockerfile no build de vários estágios. Elas
melhoram a eficiência da compilação da imagem final — apenas as camadas necessárias são recompiladas quando ocorrem
alterações. Quando você não precisar mais das imagens intermediárias, exclua-as usando o comando docker rmi.
Pode haver uma expectativa de que a imagem de produção ou versão seja menor em comparação com a imagem
dev. Devido ao mapeamento do volume, o depurador e o aplicativo estavam em execução 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.
Recursos adicionais
Desenvolvimento de contêiner com o Visual Studio
Azure Service Fabric: prepare seu ambiente de desenvolvimento
Implantar um aplicativo .NET em um contêiner do Windows no Azure Service Fabric
Solucionar problemas de desenvolvimento do Visual Studio 2017 com o Docker
Repositório do GitHub para Ferramentas do Visual Studio para Docker
Configure o ASP.NET Core para trabalhar com
servidores proxy e balanceadores de carga
08/10/2018 • 26 minutes 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();
}
NOTE
Se nenhuma ForwardedHeadersOptions é especificada em Startup.ConfigureServices ou diretamente para o
método de extensão com UseForwardedHeaders(IApplicationBuilder, ForwardedHeadersOptions), os cabeçalhos
padrão para encaminhar são ForwardedHeaders.None. A propriedade
ForwardedHeadersOptions.ForwardedHeaders deve ser configurada com os cabeçalhos para encaminhar.
Configuração de Nginx
Para encaminhar os cabeçalhos X-Forwarded-For e X-Forwarded-Proto , consulte Host ASP.NET Core no
Linux com Nginx. Para obter mais informações, veja NGINX: usando o cabeçalho encaminhado.
Configuração do Apache
X-Forwarded-For é adicionado automaticamente (veja mod_proxy do módulo do Apache: cabeçalhos de
solicitação de proxy reverso). Para obter informações sobre como encaminhar o cabeçalho
X-Forwarded-Proto , consulte Hospedar o ASP.NET Core no Linux com o Apache.
OPÇÃO DESCRIÇÃO
O padrão é X-Forwarded-For .
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 .
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 .
O padrão é X-Original-Proto .
Esse código pode ser desabilitado com uma variável de ambiente ou outra definição de configuração em
um ambiente de preparo ou de desenvolvimento.
Lidar com o caminho base e proxies que alteram o caminho da solicitação
Alguns proxies passam o caminho intacto, mas com um caminho base de aplicativo que deve ser
removido para que o roteamento funcione corretamente. O middleware de
UsePathBaseExtensions.UsePathBase 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 , o
middleware define 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 reaplicados quando o middleware é chamado novamente na
ordem inversa. Para obter mais informações sobre o processamento de ordem de middleware, consulte
Middleware do ASP.NET Core.
Se o proxy cortar o caminho (por exemplo, encaminhando /foo/api/1 para /api/1 ), corrija
redirecionamentos e links definindo a propriedade PathBase da solicitação:
Se o proxy estiver adicionando dados de caminho, descarte a parte do caminho para corrigir
redirecionamentos e links usando StartsWithSegments (PathString, PathString) e atribuindo para a
propriedade Path:
return next();
});
Solução de problemas
Quando os cabeçalhos não são encaminhados conforme o esperado, habilite registro em log. Se os logs
não fornecerem informações suficientes para solucionar o problema, enumere os cabeçalhos de
solicitação recebidos pelo servidor. Use middleware embutido para gravar cabeçalhos de solicitação para
uma resposta do aplicativo ou para log dos cabeçalhos. Coloque um dos exemplos de código a seguir
imediatamente após a chamada para UseForwardedHeaders em Startup.Configure .
Para escrever os cabeçalhos na resposta do aplicativo, use o seguinte middleware embutido terminal:
app.Run(async (context) =>
{
context.Response.ContentType = "text/plain";
// 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}");
});
Também é possível escrever nos logs em vez do corpo da resposta usando o seguinte middleware
embutido. Isso permite que o site funcione normalmente durante a depuração.
// Headers
foreach (var header in context.Request.Headers)
{
logger.LogDebug("Header: {KEY}: {VALUE}", header.Key, header.Value);
}
// Connection: RemoteIp
logger.LogDebug("Request RemoteIp: {REMOTE_IP_ADDRESS}",
context.Connection.RemoteIpAddress);
await next();
});
No exemplo anterior, 10.0.0.100 é um servidor proxy. Se o servidor for um proxy confiável, adicione o
endereço IP do servidor a KnownProxies (ou adicione uma rede confiável a KnownNetworks ) em
Startup.ConfigureServices . Para obter mais informações, consulte a seção Opções de middleware de
cabeçalhos encaminhados.
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});
IMPORTANT
Permitir que somente proxies e redes confiáveis encaminhem os cabeçalhos. Caso contrário, podem ocorrer
ataques de falsificação de IP.
Recursos adicionais
Hospedar o ASP.NET Core em um web farm
Microsoft Security Advisory CVE -2018-0787: vulnerabilidade de elevação de privilégio do ASP.NET
Core
Hospedar o ASP.NET Core em um web farm
12/12/2018 • 9 minutes to read • Edit Online
Configuração geral
Hospedar e implantar o ASP.NET Core
Aprenda como configurar ambientes de hospedagem e implantar aplicativos ASP.NET Core. Configure um
gerenciador de processo em cada nó do web farm para automatizar o início e a reinicialização do aplicativo.
Cada nó requer o tempo de execução do ASP.NET Core. Para saber mais, confira os tópicos na área Hospedar e
implantar da documentação.
Configure o ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga
Saiba mais sobre a configuração para aplicativos hospedados por trás de servidores proxy e balanceadores de
carga, o que muitas vezes oculta informações de solicitação importantes.
Implantar aplicativos ASP.NET Core no Serviço de Aplicativo do Azure
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. O Serviço de Aplicativo é uma plataforma totalmente
gerenciada que fornece escalabilidade automática, balanceamento de carga, aplicação de patch e implantação
contínua.
Dados do aplicativo
Quando um aplicativo é dimensionado para várias instâncias, pode haver um estado do aplicativo que exija o
compartilhamento entre os nós. Se o estado for transitório, considere a possibilidade de compartilhar um
IDistributedCache. Se o estado compartilhado exigir persistência, considere armazenar o estado compartilhado
em um banco de dados.
Configuração necessária
O serviço de cache e a proteção de dados exigem uma configuração para aplicativos implantados em um web
farm.
Proteção de Dados
O sistema de proteção de dados do ASP.NET Core é usado por aplicativos para proteger os dados. A proteção
de dados baseia-se em um conjunto de chaves de criptografia armazenados em um token de autenticação.
Quando o sistema de Proteção de dados é inicializado, ele aplica as configurações padrão que armazenam
localmente o token de autenticação. Sob a configuração padrão, um token de autenticação exclusivo é
armazenado em cada nó do web farm. Consequentemente, cada nó do web farm não pode descriptografar os
dados criptografados por um aplicativo em qualquer outro nó. A configuração padrão normalmente não é
adequada para hospedagem de aplicativos em um web farm. Uma alternativa à implementação de um token de
autenticação compartilhado é sempre rotear as solicitações do usuário para o mesmo nó. Para saber mais sobre
a configuração do sistema de Proteção de dados para implantações de web farm, confira Configurar a proteção
de dados do ASP.NET Core.
Cache
Em um ambiente de web farm, o mecanismo de cache deve compartilhar os itens em cache pelos nós do web
farm. O serviço de cache deve contar com um cache Redis comum, um banco de dados compartilhado do SQL
Server ou uma implementação personalizada de cache que compartilha os itens em cache pelo web farm. Para
obter mais informações, consulte O cache no ASP.NET Core distribuído.
Componentes dependentes
Os cenários a seguir não exigem configuração adicional, mas dependem de tecnologias que exigem
configurações para web farms.
CENÁRIO DEPENDE DE …
Solução de problemas
Proteção de dados e cache
Quando a proteção de dados ou cache não está configurada para um ambiente de web farm, erros intermitentes
ocorrem quando as solicitações são processadas. Isso acontece porque os nós não compartilham os mesmos
recursos e as solicitações do usuário não são sempre roteadas para o mesmo nó.
Considere um usuário que entra no aplicativo usando a autenticação de cookie. O usuário entra no aplicativo em
um nó do web farm. Se a sua próxima solicitação chegar ao mesmo nó no qual ele se conectou, o aplicativo
consegue descriptografar o cookie de autenticação e permite o acesso ao recurso do aplicativo. Se a sua próxima
solicitação chegar em um nó diferente, o aplicativo não consegue descriptografar o cookie de autenticação a
partir do nó em que o usuário entrou e a autorização do recurso solicitado falhará.
Quando qualquer um dos seguintes sintomas ocorrem de forma intermitente, o problema normalmente é
rastreado, indicando a configuração incorreta de proteção de dados ou cache para um ambiente de web farm:
Quebras de autenticação – o cookie de autenticação está configurado incorretamente ou não pode ser
descriptografado. Falha de login OpenIdConnect ou OAuth (Facebook, Microsoft, Twitter) com o erro "Falha
de correlação".
Quebras de autorização – a identidade foi perdida.
O estado de sessão perde os dados.
Os itens em cache desaparecem.
O TempData falhará.
Os POSTs falham – a verificação antifalsificação falhará.
Para saber mais sobre a configuração de Proteção de dados para implantações de web farm, confira Configurar
a proteção de dados do ASP.NET Core. Para saber mais sobre a configuração de cache para implantações de
web farm, confira O cache no ASP.NET Core distribuído.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
C:\Webs\Web1>dotnet publish
Microsoft (R) Build Engine version 15.3.409.57025 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
O comando dotnet publish chama MSBuild, que invoca o destino Publish . Quaisquer 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 do 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 o Visual Studio 2017 (ou posterior) para criar um perfil de publicação. Uma vez criado, é possível
publicar do Visual Studio ou da linha de comando.
Perfis de publicação podem simplificar o processo de publicação e podem existir em qualquer número. Crie um
perfil de publicação no Visual Studio escolhendo um dos seguintes caminhos:
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Publicar.
Selecione Publicar {NOME DO PROJETO } no menu Build.
A guia Publicar da página de capacidades do aplicativo é exibida. Se o projeto não tem um perfil de publicação,
a página a seguir é exibida:
Quando a opção Pasta é selecionada, especifique um caminho de pasta para armazenar os ativos publicados. A
pasta padrão é bin\Release\PublishOutput. Clique no botão Criar Perfil para concluir.
Depois de um perfil de publicação ser criado, a guia Publicar é alterada. O perfil recém-criado é exibido em
uma lista suspensa. Clique em Criar novo perfil para fazer isso.
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, confira Quais opções de publicação são adequadas para mim.
Quando você cria um perfil de publicação com o Visual Studio, um arquivo do MSBuild
Properties/PublishProfiles/{NOME DO PERFIL }.pubxml é criado. O .pubxml é um arquivo do MSBuild e contém
definições de configuração de publicação. Esse arquivo pode ser alterado para personalizar o processo de build
e de publicação. Esse arquivo é lido pelo processo de publicação. <LastUsedBuildConfiguration> é especial
porque é uma propriedade global e não deverá estar em nenhum arquivo importado no build. Para obter mais
informações, confira MSBuild: como definir a propriedade de configuração.
Ao publicar em um destino do Azure, o arquivo .pubxml contém o identificador de assinatura do Azure. Com
esse tipo de destino, não é recomendável adicionar esse arquivo ao controle do código-fonte. Ao publicar em
um destino não Azure, é seguro fazer check-in do arquivo .pubxml.
Informações confidenciais (como a senha de publicação) são criptografadas em um nível por
usuário/computador. Elas são armazenadas no arquivo Properties/PublishProfiles/{NOME DO
PERFIL }.pubxml.user. Já que esse arquivo pode armazenar informações confidenciais, o check-in dele não deve
ser realizado no controle do código-fonte.
Para obter uma visão geral de como publicar um aplicativo Web do ASP.NET Core, veja Hospedar e implantar.
As tarefas e os destinos de MSBuild necessários para publicar um aplicativo ASP.NET Core são de software
livre no https://github.com/aspnet/websdk.
O dotnet publish pode usar os perfis de publicação de pasta, MSDeploy e Kudu:
Pasta (funciona em modo multiplataforma):
MSDeploy (atualmente só funciona no Windows, uma vez que o MSDeploy não é multiplataforma):
Pacote MSDeploy (atualmente só funciona no Windows, uma vez que o MSDeploy não é multiplataforma):
<Project>
<PropertyGroup>
<PublishProtocol>Kudu</PublishProtocol>
<PublishSiteName>nodewebapp</PublishSiteName>
<UserName>username</UserName>
<Password>password</Password>
</PropertyGroup>
</Project>
Execute o seguinte comando para compactar os conteúdos de publicação e publicá-los no Azure usando as APIs
do Kudu:
Ao publicar com um perfil chamado FolderProfile, é possível executar qualquer um dos comandos abaixo:
dotnet build /p:DeployOnBuild=true /p:PublishProfile=FolderProfile
msbuild /p:DeployOnBuild=true /p:PublishProfile=FolderProfile
Ao invocar dotnet build, ele chama msbuild para executar o processo de build e de publicação. Chamar
dotnet build ou msbuild é equivalente ao passar 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. A propriedade de configuração <LastUsedBuildConfiguration> é especial e não deve ser
substituída em um arquivo do MSBuild importado. Essa propriedade pode ser substituída da linha de comando.
Usando a CLI do .NET Core:
Usando o MSBuild:
msbuild "AzureWebApp.csproj"
/p:DeployOnBuild=true
/p:PublishProfile="AzureWebApp - Web Deploy"
/p:Username="$AzureWebApp"
/p:Password=".........."
Um perfil de publicação também pode ser usado com o comando dotnet msbuild da CLI do .NET Core em um
prompt de comando do Windows:
NOTE
O comando dotnet msbuild está disponível em várias plataformas e pode compilar aplicativos ASP.NET Core no macOS e
Linux. No entanto, o MSBuild no macOS e Linux não é capaz de implantar um aplicativo no Azure ou em outro ponto de
extremidade do MSDeploy. O MSDeploy está disponível apenas no Windows.
Definir o ambiente
Inclua a propriedade <EnvironmentName> no perfil de publicação (.pubxml) ou no arquivo de projeto para definir
o ambiente do aplicativo:
<PropertyGroup>
<EnvironmentName>Development</EnvironmentName>
</PropertyGroup>
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, o elemento <Content> a seguir
exclui todos os arquivos de texto (.txt) da pasta wwwroot/content e de 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 ao arquivo .csproj. Quando adicionada ao
arquivo .csproj, a regra será adicionada a todos os perfis de publicação no projeto.
O elemento <MsDeploySkipRules> a seguir exclui todos os arquivos da pasta wwwroot/content:
<ItemGroup>
<MsDeploySkipRules Include="CustomSkipFolder">
<ObjectName>dirPath</ObjectName>
<AbsolutePath>wwwroot\\content</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
<MsDeploySkipRules> não exclui os destinos de ignorados do site de implantação. Pastas e arquivos de destino
de <Content> são excluídos do site de implantação. Por exemplo, suponha que um aplicativo Web implantado
tinha os seguintes arquivos:
Views/Home/About1.cshtml
Views/Home/About2.cshtml
Views/Home/About3.cshtml
Nos elementos <MsDeploySkipRules> a seguir, esses arquivos não seriam 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>
Os elementos <MsDeploySkipRules> anteriores impedem que os arquivos ignorados sejam implantados. Ele não
excluirá esses arquivos depois que eles forem implantados.
O elemento <Content> a seguir exclui os arquivos de destino no site de implantação:
<ItemGroup>
<Content Update="Views/Home/About?.cshtml" CopyToPublishDirectory="Never" />
</ItemGroup>
O uso da implantação de linha de comando com o elemento <Content> anterior produz a seguinte saída:
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 marcação a seguir inclui uma pasta images fora do diretório do projeto para a pasta wwwroot/images 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 ao arquivo
.csproj, ela será incluída em cada perfil de publicação no projeto.
A marcação realçada a seguir mostra como:
Copiar um arquivo de fora do projeto para a pasta wwwroot.
Excluir a pasta wwwroot\Content.
Excluir Views\Home\About2.cshtml.
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project.
You can customize the behavior of this process by editing this
MSBuild file.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<PublishProvider>FileSystem</PublishProvider>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<PublishFramework />
<ProjectGuid>afa9f185-7ce0-4935-9da1-ab676229d68a</ProjectGuid>
<publishUrl>bin\Release\PublishOutput</publishUrl>
<DeleteExistingFiles>False</DeleteExistingFiles>
</PropertyGroup>
<ItemGroup>
<ResolvedFileToPublish Include="..\ReadMe2.MD">
<RelativePath>wwwroot\ReadMe2.MD</RelativePath>
</ResolvedFileToPublish>
</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 serviço
Kudu. Acrescente o token scm ao nome do aplicativo Web. Por exemplo:
URL RESULTADO
Selecione o item de menu Console de Depuração para exibir, editar, excluir ou adicionar arquivos.
Recursos adicionais
A Implantação da Web (MSDeploy) simplifica a implantação de aplicativos Web e sites da Web em
servidores IIS.
https://github.com/aspnet/websdk: problemas de arquivos e recursos de solicitação para implantação.
Publicar um aplicativo Web ASP.NET em uma VM do Azure usando o Visual Studio
Estrutura do diretório do ASP.NET Core
21/01/2019 • 4 minutes to read • Edit Online
†Indica um diretório
O diretório publish representa o caminho raiz de conteúdo (também chamado de caminho base do aplicativo)
da implantação. Qualquer que seja o nome fornecido para o diretório publish do aplicativo implantado no
servidor, o local dele serve como o caminho físico do servidor para o aplicativo hospedado.
O diretório wwwroot, se presente, contém somente ativos estáticos.
Um diretório Logs pode ser criado para a implantação usando uma das duas abordagens a seguir:
Adicione o seguinte elemento <Target> ao arquivo de projeto:
O elemento <MakeDir> cria uma pasta Logs vazia na saída publicada. O elemento usa a propriedade
PublishDir para determinar o local de destino no qual criar a pasta. Vários métodos de implantação,
como a Implantação da Web, ignoram pastas vazias durante a implantação. O elemento
<WriteLinesToFile> gera um arquivo na pasta Logs, o que garante a implantação da pasta no servidor. A
criação de pasta usando essa abordagem poderá falhar se o processo de trabalho não tiver acesso de
gravação para a pasta de destino.
Crie fisicamente o diretório Logs no servidor na implantação.
O diretório de implantação requer permissões de leitura/execução. O diretório Logs requer permissões de
leitura/gravação. Diretórios adicionais em que os arquivos são gravados exigem permissões de leitura/gravação.
Log de stdout do Módulo do ASP.NET Core não exige uma pasta Logs na implantação. O módulo é capaz de
criar pastas no caminho stdoutLogFile quando o arquivo de log é criado. Criar uma pasta Logs é útil para o log
de depuração aprimorado do Módulo do ASP.NET Core. As pastas no caminho fornecido para o valor
<handlerSetting> não são criadas automaticamente pelo módulo e devem existir previamente na implantação
para permitir que o módulo grave o log de depuração.
Recursos adicionais
dotnet publish
Implantação de aplicativos do .NET Core
Estruturas de destino
Catálogo de RIDs do .NET Core
Verificações de integridade no ASP.NET Core
10/01/2019 • 35 minutes to read • Edit Online
Pré-requisitos
As verificações de integridade são normalmente usadas com um orquestrador de contêineres ou um serviço de
monitoramento externo para verificar o status de um aplicativo. Antes de adicionar verificações de integridade a
um aplicativo, decida qual sistema de monitoramento será usado. O sistema de monitoramento determina quais
tipos de verificações de integridade serão criados e como configurar seus pontos de extremidade.
Referencie o metapacote Microsoft.AspNetCore.App ou adicione uma referência de pacote ao pacote
Microsoft.AspNetCore.Diagnostics.HealthChecks.
O aplicativo de exemplo fornece um código de inicialização para demonstrar verificações de integridade para
vários cenários. O cenário investigação de banco de dados verifica a integridade de uma conexão de banco de
dados usando o BeatPulse. O cenário investigação de DbContext verifica um banco de dados usando um
DbContext do EF Core. Para explorar os cenários de banco de dados, o aplicativo de exemplo:
Outro cenário de verificação de integridade demonstra como filtrar verificações de integridade para uma porta de
gerenciamento. O aplicativo de exemplo exige a criação de um arquivo Properties/launchSettings.json que inclui a
URL de gerenciamento e a porta de gerenciamento. Para obter mais informações, confira a seção Filtrar por
porta.
Para executar o cenário de configuração básica usando o aplicativo de exemplo, execute o seguinte comando na
pasta do projeto em um shell de comando:
Exemplo do Docker
O Docker oferece uma diretiva HEALTHCHECK interna que pode ser usada para verificar o status de um aplicativo
que usa a configuração básica de verificação de integridade:
HEALTHCHECK CMD curl --fail http://localhost:5000/health || exit
if (healthCheckResultHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("The check indicates a healthy result."));
}
return Task.FromResult(
HealthCheckResult.Unhealthy("The check indicates an unhealthy result."));
}
}
A sobrecarga AddCheck mostrada no exemplo a seguir define o status de falha ( HealthStatus ) como relatório
quando a verificação de integridade relata uma falha. Se o status de falha é definido como null (padrão),
HealthStatus.Unhealthy é relatado. Essa sobrecarga é um cenário útil para autores de biblioteca, em que o status
de falha indicado pela biblioteca é imposto pelo aplicativo quando uma falha de verificação de integridade ocorre
se a implementação da verificação de integridade respeita a configuração.
Marcas podem ser usadas para filtrar as verificações de integridade (descritas posteriormente na seção Filtrar
verificações de integridade).
services.AddHealthChecks()
.AddCheck<ExampleHealthCheck>(
"example_health_check",
failureStatus: HealthStatus.Degraded,
tags: new[] { "example" });
AddCheck também pode executar uma função lambda. No seguinte exemplo, o nome da verificação de
integridade é especificado como Example e a verificação sempre retorna um estado íntegro:
Se as verificações de integridade precisarem escutar uma porta específica, use uma sobrecarga igual a
UseHealthChecks para definir a porta (descrito posteriormente na seção Filtrar por porta ):
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
Personalizar a saída
A opção ResponseWriter obtém ou define um representante usado para gravar a resposta. O representante
padrão grava uma resposta mínima de texto sem formatação com o valor de cadeia de caracteres
HealthReport.Status .
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\MSSQLLocalDB;Database=HealthCheckSample;Trusted_Connection=True;MultipleActiveResultSets=true;Conn
ectRetryCount=0"
},
"Logging": {
"LogLevel": {
"Default": "Debug"
},
"Console": {
"IncludeScopes": "true"
}
}
}
Para executar o cenário de investigação de banco de dados usando o aplicativo de exemplo, execute o seguinte
comando na pasta do projeto em um shell de comando:
NOTE
O BeatPulse não é mantido pela Microsoft nem tem o suporte da Microsoft.
Investigação de DbContext do Entity Framework Core
A verificação DbContext confirma se o aplicativo pode se comunicar com o banco de dados configurado para um
DbContext do EF Core. A verificação DbContext é compatível em aplicativos que:
services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(
Configuration["ConnectionStrings:DefaultConnection"]);
});
}
DbContextHealthStartup.cs:
Para executar o cenário de investigação DbContext usando o aplicativo de exemplo, confirme se o banco de dados
especificado pela cadeia de conexão não existe na instância do SQL Server. Se o banco de dados existir, exclua-o.
Execute o seguinte comando na pasta do projeto em um shell de comando:
Depois que o aplicativo estiver em execução, verifique o status da integridade fazendo uma solicitação para o
ponto de extremidade /health em um navegador. O banco de dados e AppDbContext não existem e, portanto, o
aplicativo fornece a seguinte resposta:
Unhealthy
Dispare o aplicativo de exemplo para criar o banco de dados. Faça uma solicitação para /createdatabase .O
aplicativo responde:
Faça uma solicitação ao ponto de extremidade /health . O banco de dados e o contexto existem e, portanto, o
aplicativo responde:
Healthy
Dispare o aplicativo de exemplo para excluir o banco de dados. Faça uma solicitação para /deletedatabase .O
aplicativo responde:
Faça uma solicitação ao ponto de extremidade /health . O aplicativo fornece uma resposta não íntegra:
Unhealthy
return Task.FromResult(
HealthCheckResult.Unhealthy("The startup task is still running."));
}
}
_startupHostedServiceHealthCheck.StartupTaskCompleted = true;
return Task.CompletedTask;
}
return Task.CompletedTask;
}
services.AddHealthChecks()
.AddCheck<StartupHostedServiceHealthCheck>(
"hosted_service_startup",
failureStatus: HealthStatus.Degraded,
tags: new[] { "ready" });
}
Em um navegador, visite /health/ready várias vezes até terem decorrido 15 segundos. A verificação de
integridade relata Unhealthy para os primeiros 15 segundos. Após 15 segundos, o ponto de extremidade relata
Healthy , que reflete a conclusão da tarefa de execução longa pelo serviço hospedado.
Exemplo do Kubernetes
O uso de verificações de preparação e atividade separadas é útil em um ambiente como o Kubernetes. No
Kubernetes, um aplicativo pode precisar executar um trabalho de inicialização demorado antes de aceitar
solicitações, como um teste da disponibilidade do banco de dados subjacente. O uso de verificações separadas
permite que o orquestrador distinga se o aplicativo está funcionando, mas ainda não está pronto, ou se o
aplicativo falhou ao ser iniciado. Para obter mais informações sobre as investigações de preparação e atividade no
Kubernetes, confira Configurar investigações de preparação e atividade na documentação do Kubernetes.
O seguinte exemplo demonstra uma configuração de investigação de preparação do Kubernetes:
spec:
template:
spec:
readinessProbe:
# an http probe
httpGet:
path: /health/ready
port: 80
# length of time to wait for a pod to initialize
# after pod startup, before applying health checking
initialDelaySeconds: 30
timeoutSeconds: 1
ports:
- containerPort: 80
Para executar a investigação baseada em métrica com a saída do gravador de resposta personalizada usando o
aplicativo de exemplo, execute o seguinte comando na pasta do projeto em um shell de comando:
NOTE
O BeatPulse inclui cenários de verificação de integridade baseada em métrica, incluindo verificações de atividade de valor
máximo e armazenamento em disco.
O BeatPulse não é mantido pela Microsoft nem tem o suporte da Microsoft.
ManagementPortStartup.cs:
Para executar o cenário de configuração de porta de gerenciamento usando o aplicativo de exemplo, execute o
seguinte comando na pasta do projeto em um shell de comando:
namespace SampleApp
{
public class ExampleHealthCheck : IHealthCheck
{
private readonly string _data1;
private readonly int? _data2;
return HealthCheckResult.Healthy();
}
catch (AccessViolationException ex)
{
return new HealthCheckResult(
context.Registration.FailureStatus,
description: "An access violation occurred during the check.",
exception: ex,
data: null);
}
}
}
}
2. Escreva um método de extensão com parâmetros que o aplicativo de consumo chama em seu método
Startup.Configure . No seguinte exemplo, suponha a seguinte assinatura de método de verificação de
integridade:
A assinatura anterior indica que a ExampleHealthCheck exige dados adicionais para processar a lógica de
investigação de verificação de integridade. Os dados são fornecidos para o representante usado para criar
a instância de verificação de integridade quando a verificação de integridade é registrada em um método
de extensão. No seguinte exemplo, o chamador especifica itens opcionais:
nome da verificação de integridade ( name ). Se null , example_health_check é usado.
ponto de dados de cadeia de caracteres para a verificação de integridade ( data1 ).
ponto de dados de inteiro para a verificação de integridade ( data2 ). Se null , 1 é usado.
status de falha ( HealthStatus ). O padrão é null . Se null , HealthStatus.Unhealthy é relatado para um
status de falha.
marcas ( IEnumerable<string> ).
using System.Collections.Generic;
using Microsoft.Extensions.Diagnostics.HealthChecks;
NOTE
O BeatPulse inclui publicadores para vários sistemas, incluindo o Application Insights.
O BeatPulse não é mantido pela Microsoft nem tem o suporte da Microsoft.
Hospedar e implantar Componentes Razor
05/02/2019 • 25 minutes to read • Edit Online
NOTE
O Razor Components do ASP.NET Core é compatível com ASP.NET Core 3.0 ou posterior.
Blazor é uma estrutura da Web sem suporte e experimental que não deve ser usada para cargas de trabalho de produção no
momento.
Publique o aplicativo
Os aplicativos são publicados para implantação na configuração de versão com o comando dotnet publish. Um
IDE pode manipular a execução do comando dotnet publish automaticamente usando recursos de publicação
internos, para que não seja necessário executar o comando manualmente usando um prompt de comando,
dependendo das ferramentas de desenvolvimento em uso.
O dotnet publish dispara uma restauração das dependências do projeto e compila o projeto antes de criar os
ativos para implantação. Como parte do processo de build, os assemblies e métodos não usados são removidos
para reduzir o tamanho de download do aplicativo e os tempos de carregamento. A implantação é criada na pasta
/bin/Release/<target-framework>/publish.
Os ativos na pasta publish são implantados no servidor Web. A implantação pode ser um processo manual ou
automatizado, dependendo das ferramentas de desenvolvimento em uso.
Adicione uma entrada ao arquivo launchSettings.json do aplicativo no perfil do IIS Express. Essa
configuração é obtida ao executar o aplicativo com o Depurador do Visual Studio e ao executar o aplicativo
em um prompt de comando com dotnet run .
"commandLineArgs": "--contentroot=/<content-root>"
No Visual Studio, especifique o argumento em Propriedades > Depurar > Argumentos de aplicativo. A
configuração do argumento na página de propriedades do Visual Studio adiciona o argumento ao arquivo
launchSettings.json.
--contentroot=/<content-root>
Caminho base
O argumento --pathbase define o caminho base do aplicativo para um aplicativo executado localmente com um
caminho virtual não raiz (a tag href <base> é definida como um caminho diferente de / para preparo e
produção). Para obter mais informações, confira a seção Caminho base do aplicativo.
IMPORTANT
Ao contrário do caminho fornecido ao href da tag <base> , não inclua uma barra à direita ( / ) ao passar o valor do
argumento --pathbase . Se o caminho base do aplicativo for fornecido na tag <base> como
<base href="/CoolApp/" /> (inclui uma barra à direita), passe o valor do argumento de linha de comando como
--pathbase=/CoolApp (nenhuma barra à direita).
Adicione uma entrada ao arquivo launchSettings.json do aplicativo no perfil do IIS Express. Essa
configuração é obtida ao executar o aplicativo com o Depurador do Visual Studio e ao executar o aplicativo
em um prompt de comando com dotnet run .
"commandLineArgs": "--pathbase=/<virtual-path>"
No Visual Studio, especifique o argumento em Propriedades > Depurar > Argumentos de aplicativo. A
configuração do argumento na página de propriedades do Visual Studio adiciona o argumento ao arquivo
launchSettings.json.
--pathbase=/<virtual-path>
URLs
O argumento --urls indica os endereços IP ou os endereços de host com portas e protocolos para escutar
solicitações.
Passe o argumento ao executar o aplicativo localmente em um prompt de comando. No diretório do
aplicativo, execute:
Adicione uma entrada ao arquivo launchSettings.json do aplicativo no perfil do IIS Express. Essa
configuração é obtida ao executar o aplicativo com o Depurador do Visual Studio e ao executar o aplicativo
em um prompt de comando com dotnet run .
"commandLineArgs": "--urls=http://127.0.0.1:0"
No Visual Studio, especifique o argumento em Propriedades > Depurar > Argumentos de aplicativo. A
configuração do argumento na página de propriedades do Visual Studio adiciona o argumento ao arquivo
launchSettings.json.
--urls=http://127.0.0.1:0
<handlers>
<remove name="aspNetCore" />
</handlers>
Como alternativa, desabilite a herança da seção <system.webServer> do aplicativo raiz (pai) usando um elemento
<location> com inheritInChildApplications definido como false :
A ação de remover o manipulador ou desabilitar a herança é realizada além da ação da configurar o caminho base do
aplicativo, conforme descrito nesta seção. Defina o caminho base do aplicativo no arquivo index.html do aplicativo do alias
do IIS usado ao configurar o subaplicativo no IIS.
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /Index.html =404;
}
}
}
Para obter mais informações sobre a configuração do servidor Web Nginx de produção, confira Creating NGINX
Plus and NGINX Configuration Files (Criando arquivos de configuração do NGINX Plus e do NGINX).
Hospedagem autônoma do Blazor do lado do cliente no Docker
Para hospedar o Blazor no Docker usando o Nginx, configure o Dockerfile para usar a imagem do Nginx baseada
no Alpine. Atualize o Dockerfile para copiar o arquivo nginx.config no contêiner.
Adicione uma linha ao Dockerfile, conforme é mostrado no exemplo a seguir:
FROM nginx:alpine
COPY ./bin/Release/netstandard2.0/publish /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/nginx.conf
NOTE
O Razor Components do ASP.NET Core é compatível com ASP.NET Core 3.0 ou posterior.
Blazor é uma estrutura da Web sem suporte e experimental que não deve ser usada para cargas de trabalho de produção
no momento.
O Blazor executa a vinculação de IL (linguagem intermediária) durante cada build do Modo de Versão para
remover IL desnecessária dos assemblies de saída.
Você pode controlar a vinculação do assembly com uma das seguintes abordagens:
Desabilite a vinculação globalmente com uma propriedade MSBuild.
Controle a vinculação por assembly usando um arquivo de configuração.
<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>
Para saber mais sobre o formato de arquivo para o arquivo de configuração, veja Vinculador de IL: sintaxe do
descritor de xml.
Especifique o arquivo de configuração no arquivo de projeto com o item BlazorLinkerDescriptor :
<ItemGroup>
<BlazorLinkerDescriptor Include="Linker.xml" />
</ItemGroup>
Visão geral sobre a segurança do ASP.NET Core
09/01/2019 • 3 minutes 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 HTTPS, 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.
AddDefaultIdentity e AddIdentity
AddDefaultIdentity foi introduzido no ASP.NET Core 2.1. Chamar AddDefaultIdentity é semelhante a
chamar o seguinte:
AddIdentity
AddDefaultUI
AddDefaultTokenProviders
Ver AddDefaultIdentity fonte para obter mais informações.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
O código anterior configura a identidade com os valores de opção padrão. Serviços são disponibilizados
para o aplicativo por meio injeção de dependência.
Identidade é habilitada por meio da chamada UseAuthentication. UseAuthentication Adiciona a
autenticação middleware ao pipeline de solicitação.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
// 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();
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Configure Identity
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;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// Cookie settings
options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromDays(150);
options.Cookies.ApplicationCookie.LoginPath = "/Account/Login";
// User settings
options.User.RequireUniqueEmail = true;
});
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?}");
});
}
Quando um usuário clica o registre link, o Register ação é invocada em AccountController . O Register
ação cria o usuário chamando CreateAsync sobre o _userManager objeto (fornecidas para
AccountController pela 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: Ver confirmação de conta para obter as etapas impedir que o logon imediata no momento do
registro.
Fazer Logon
O formulário de logon é exibido quando:
O faça logon no link é selecionado.
Um usuário tenta acessar uma página restrita que eles não estão autorizados a acessar ou quando eles
ainda não foram autenticados pelo sistema.
Quando o formulário na página de logon é enviado, o OnPostAsync ação é chamada. PasswordSignInAsync é
chamado de _signInManager (fornecido pela injeção de dependência) do objeto.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
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(Input.Email,
Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe =
Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
A base Controller classe expõe um User propriedade que você pode acessar métodos do controlador. Por
exemplo, você pode enumerar User.Claims e tomar decisões de autorização. Para obter mais informações,
consulte Introdução à autorização no núcleo do ASP.NET.
O formulário de logon é exibido quando os usuários selecionam o faça logon no vincular ou será
redirecionado ao acessar uma página que requer autenticaçã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 ou PageModel ) classe expõe um User propriedade. Por exemplo, User.Claims
podem ser enumeradas para tomar decisões de autorização.
Fazer logoff
O fazer logoff link invoca o LogoutModel.OnPost ação.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace WebApp1.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LogoutModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<LogoutModel> _logger;
SignOutAsync limpa as declarações do usuário armazenadas em um cookie. Não redirecionar após chamar
SignOutAsync ou o usuário irá não ser desconectado.
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity"
asp-page="/Account/Manage/Index"
title="Manage">[email protected]!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout"
asp-route-returnUrl="@Url.Page("/", new { area = "" })"
method="post">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
//
// 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");
}
Identidade de teste
Os modelos de projeto da web padrão permitem acesso anônimo para as home pages. Para testar a
identidade, adicione [Authorize] para a página de privacidade.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebApp1.Pages
{
[Authorize]
public class PrivacyModel : PageModel
{
public void OnGet()
{
}
}
}
Se você estiver entrado, saia do serviço. Execute o aplicativo e selecione o privacidade link. Você será
redirecionado à página de logon.
Explore a identidade
Para explorar a identidade em mais detalhes:
Criar fonte de interface do usuário de identidade completa
Examine a origem de cada página e percorrer o depurador.
Componentes de identidade
Todos os identidade NuGet pacotes dependentes são incluídos na metapacote Microsoft.
O pacote principal para a identidade está Microsoft.AspNetCore.Identity. Este pacote contém o conjunto
principal de interfaces para ASP.NET Core Identity e é incluído por
Microsoft.AspNetCore.Identity.EntityFrameworkCore .
Próximas etapas
Configurar o Identity
Criar um aplicativo ASP.NET Core com os dados de usuário protegidos por autorização
Adicionar, baixar e excluir dados de usuário à identidade em um projeto ASP.NET Core
Habilitar a geração de código QR TOTP para aplicativos de autenticador no ASP.NET Core
Migrar autenticação e identidade para o ASP.NET Core
Confirmação de conta e de recuperação de senha no ASP.NET Core
Autenticação de dois fatores com SMS no ASP.NET Core
Hospedar o ASP.NET Core em um web farm
Identidade de Scaffold em projetos ASP.NET Core
26/10/2018 • 22 minutes to read • Edit Online
NOTE
Os serviços são necessários ao usar autenticação de dois fatores, recuperação de confirmação e a senha da contae outros
recursos de segurança com identidade. Serviços ou stubs de serviço não são gerados quando o scaffolding de identidade.
Serviços para habilitar esses recursos devem ser adicionados manualmente. Por exemplo, consulte exigem Email de
confirmaçã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();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
UseHsts é recomendável, mas não é necessário. Ver protocolo de segurança de transporte estrito HTTP para
obter mais informações.
Exige que o código de banco de dados de identidade gerado migrações do Entity Framework Core. Criar uma
migração e atualizar o banco de dados. Por exemplo, execute os seguintes comandos:
Visual Studio
CLI do .NET Core
No Visual Studio Package Manager Console:
Add-Migration CreateIdentitySchema
Update-Database
Add-Migration CreateIdentitySchema
Update-Database
Habilitar a autenticação
No Configure método da Startup classe, chame UseAuthentication depois UseStaticFiles :
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
UseHsts é recomendável, mas não é necessário. Ver protocolo de segurança de transporte estrito HTTP para
obter mais informações.
Alterações de layout
Opcional: Adicionar o logon parcial ( _LoginPartial ) para o arquivo de layout:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorNoAuth8</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">RazorNoAuth8</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="/Contact">Contact</a></li>
</ul>
<partial name="_LoginPartial" />
</div>
</div>
</nav>
<environment include="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 exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
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>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - MvcNoAuth3</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-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MvcNoAuth3</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>
<partial name="_LoginPartial" />
</div>
</div>
</nav>
<environment include="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>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.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>
Add-Migration CreateIdentitySchema
Update-Database
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
}
UseHsts é recomendável, mas não é necessário. Ver protocolo de segurança de transporte estrito HTTP para
obter mais informações.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
// services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
});
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
// using Microsoft.AspNetCore.Identity.UI.Services;
services.AddSingleton<IEmailSender, EmailSender>();
}
services.AddIdentity<IdentityUser, IdentityRole>()
// services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
Registrar um IEmailSender implementação, por exemplo:
// using Microsoft.AspNetCore.Identity.UI.Services;
services.AddSingleton<IEmailSender, EmailSender>();
Recursos adicionais
Alterações no código de autenticação para o ASP.NET Core 2.1 e posterior
Adicionar, baixar e excluir dados de usuário
personalizada à identidade em um projeto ASP.NET
Core
09/12/2018 • 10 minutes to read • Edit Online
Pré-requisitos
SDK do .NET Core 2.1 ou posteriores
using Microsoft.AspNetCore.Identity;
using System;
namespace WebApp1.Areas.Identity.Data
{
public class WebApp1User : IdentityUser
{
[PersonalData]
public string Name { get; set; }
[PersonalData]
public DateTime DOB { get; set; }
}
}
public IndexModel(
UserManager<WebApp1User> userManager,
SignInManager<WebApp1User> signInManager,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
}
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
Username = userName;
return Page();
}
if (Input.Name != user.Name)
{
user.Name = Input.Name;
}
if (Input.DOB != user.DOB)
{
user.DOB = Input.DOB;
}
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", Model.StatusMessage)
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" disabled />
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
@if (Model.IsEmailConfirmed)
{
<div class="input-group">
<input asp-for="Input.Email" class="form-control" />
<span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok
text-success"></span></span>
</div>
}
else
{
<input asp-for="Input.Email" class="form-control" />
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail"
class="btn btn-link">Send verification email</button>
}
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
</div>
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
[BindProperty]
public InputModel Input { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.",
MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"]</h2>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
<span asp-validation-for="Input.DOB" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Compile o projeto.
Adicionar uma migração para os dados de usuário personalizada
Visual Studio
CLI do .NET Core
No Visual Studio Package Manager Console:
Add-Migration CustomUserData
Update-Database
Teste de criar, exibir, baixar, excluir dados de usuário personalizada
Teste o aplicativo:
Registre um novo usuário.
Exibir os dados de usuário personalizada no /Identity/Account/Manage página.
Baixar e exibir os dados pessoais de usuários da /Identity/Account/Manage/PersonalData página.
Amostras de autenticação para o ASP.NET Core
30/01/2019 • 2 minutes to read • Edit Online
Executar os exemplos
Selecione uma ramificação. Por exemplo, release/2.2
Clone ou baixe o repositório do ASP.NET Core.
Verifique se você instalou o SDK do .NET Core versão que corresponde o clone do repositório do ASP.NET
Core.
Navegue até um exemplo na AspNetCore/src/Security/samples e executar o exemplo com dotnet run .
Personalização de modelo de identidade no ASP.NET
Core
27/09/2018 • 30 minutes to read • Edit Online
O modelo de identidade
Tipos de entidade
O modelo de identidade consiste dos seguintes tipos de entidade.
TIPO DE ENTIDADE DESCRIÇÃO
builder.Entity<TUser>(b =>
{
// Primary key
b.HasKey(u => u.Id);
// Each User can have many entries in the UserRole join table
b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});
builder.Entity<TUserClaim>(b =>
{
// Primary key
b.HasKey(uc => uc.Id);
builder.Entity<TUserLogin>(b =>
{
// Composite primary key consisting of the LoginProvider and the key to use
// with that provider
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
// Limit the size of the composite key columns due to common DB restrictions
b.Property(l => l.LoginProvider).HasMaxLength(128);
b.Property(l => l.ProviderKey).HasMaxLength(128);
builder.Entity<TUserToken>(b =>
{
// Composite primary key consisting of the UserId, LoginProvider and Name
b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });
// Limit the size of the composite key columns due to common DB restrictions
b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
b.Property(t => t.Name).HasMaxLength(maxKeyLength);
builder.Entity<TRole>(b =>
{
// Primary key
b.HasKey(r => r.Id);
builder.Entity<TRoleClaim>(b =>
{
// Primary key
b.HasKey(rc => rc.Id);
builder.Entity<TUserRole>(b =>
{
// Primary key
b.HasKey(r => new { r.UserId, r.RoleId });
Em vez de usar esses tipos diretamente, os tipos podem ser usados como classes base para os tipos do aplicativo.
O DbContext classes definidas pela identidade são genéricas, de modo que diferentes tipos CLR podem ser usados
para uma ou mais dos tipos de entidade no modelo. Esses tipos genéricos também permitem que o User tipo de
dados (PK) chave primária a ser alterado.
Ao usar a identidade com suporte para funções, um IdentityDbContext classe deve ser usada. Por exemplo:
// Uses all the built-in Identity types
// Uses `string` as the key type
public class IdentityDbContext
: IdentityDbContext<IdentityUser, IdentityRole, string>
{
}
// Uses the built-in Identity types except with a custom User type
// Uses `string` as the key type
public class IdentityDbContext<TUser>
: IdentityDbContext<TUser, IdentityRole, string>
where TUser : IdentityUser
{
}
// Uses the built-in Identity types except with custom User and Role types
// The key type is defined by TKey
public class IdentityDbContext<TUser, TRole, TKey> : IdentityDbContext<
TUser, TRole, TKey, IdentityUserClaim<TKey>, IdentityUserRole<TKey>,
IdentityUserLogin<TKey>, IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where TKey : IEquatable<TKey>
{
}
// No built-in Identity types are used; all are specified by generic arguments
// The key type is defined by TKey
public abstract class IdentityDbContext<
TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken>
: IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken>
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where TKey : IEquatable<TKey>
where TUserClaim : IdentityUserClaim<TKey>
where TUserRole : IdentityUserRole<TKey>
where TUserLogin : IdentityUserLogin<TKey>
where TRoleClaim : IdentityRoleClaim<TKey>
where TUserToken : IdentityUserToken<TKey>
Também é possível usar a identidade sem funções (apenas declarações), caso em que um
IdentityUserContext<TUser> classe deve ser usada:
// Uses the built-in non-role Identity types except with a custom User type
// Uses `string` as the key type
public class IdentityUserContext<TUser>
: IdentityUserContext<TUser, string>
where TUser : IdentityUser
{
}
// Uses the built-in non-role Identity types except with a custom User type
// The key type is defined by TKey
public class IdentityUserContext<TUser, TKey> : IdentityUserContext<
TUser, TKey, IdentityUserClaim<TKey>, IdentityUserLogin<TKey>,
IdentityUserToken<TKey>>
where TUser : IdentityUser<TKey>
where TKey : IEquatable<TKey>
{
}
// No built-in Identity types are used; all are specified by generic arguments, with no roles
// The key type is defined by TKey
public abstract class IdentityUserContext<
TUser, TKey, TUserClaim, TUserLogin, TUserToken> : DbContext
where TUser : IdentityUser<TKey>
where TKey : IEquatable<TKey>
where TUserClaim : IdentityUserClaim<TKey>
where TUserLogin : IdentityUserLogin<TKey>
where TUserToken : IdentityUserToken<TKey>
{
}
Personalizar o modelo
O ponto de partida para a personalização de modelo é derivado do tipo de contexto apropriado. Consulte a
modelar tipos genéricos seção. Esse tipo de contexto geralmente é chamado ApplicationDbContext e é criada pelos
modelos do ASP.NET Core.
O contexto é usado para configurar o modelo de duas maneiras:
Fornecimento de entidade e tipos de chaves para os parâmetros de tipo genérico.
Substituindo OnModelCreating para modificar o mapeamento desses tipos.
Ao substituir OnModelCreating , base.OnModelCreating deve ser chamado pela primeira vez; a configuração de
substituição deve ser chamada em seguida. O EF Core geralmente tem uma política last-one-wins para a
configuração. Por exemplo, se o ToTable método para um tipo de entidade é chamado pela primeira vez com o
nome de uma tabela e, em seguida, novamente mais tarde com um nome de tabela diferente, o nome da tabela na
segunda chamada é usado.
Dados de usuário personalizada
Dados de usuário personalizados há suporte para herdar de IdentityUser . É comum para esse tipo de nome
ApplicationUser :
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI();
No ASP.NET Core 2.1 ou posterior, a identidade é fornecida como uma biblioteca de classes Razor. Para obter mais
informações, consulte Identidade de Scaffold em projetos ASP.NET Core. Consequentemente, o código anterior
requer uma chamada para AddDefaultUI. Se o scaffolder de identidade foi usado para adicionar arquivos de
identidade para o projeto, remova a chamada para AddDefaultUI . Para obter mais informações, consulte:
Identidade Scaffold
Adicionar, baixar e excluir dados de usuário personalizada à identidade
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext, Guid>()
.AddDefaultTokenProviders();
2. Depois de confirmar a exclusão do banco de dados, remova a migração inicial com Remove-Migration (PMC )
ou dotnet ef migrations remove (CLI do .NET Core).
3. Atualizar o ApplicationDbContext classe para derivar de IdentityDbContext<TUser,TRole,TKey>. Especifique
o novo tipo de chave para TKey . Por exemplo, para usar um Guid tipo de chave:
public class ApplicationDbContext
: IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
services.AddDefaultIdentity<IdentityUser<Guid>>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentity<IdentityUser<Guid>, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentity<IdentityUser<Guid>, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext, Guid>()
.AddDefaultTokenProviders();
4. Se um personalizado ApplicationUser classe está sendo usado, atualize a classe para herdar de
IdentityUser . Por exemplo:
using System;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using System;
using Microsoft.AspNetCore.Identity;
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext, Guid>()
.AddDefaultTokenProviders();
O AddEntityFrameworkStores método aceita um TKey tipo que indica o tipo de dados da chave primária.
5. Se um personalizado ApplicationRole classe está sendo usado, atualize a classe para herdar de
IdentityRole<TKey> . Por exemplo:
using System;
using Microsoft.AspNetCore.Identity;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
using System;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
});
services.AddSingleton<IEmailSender, EmailSender>();
}
using System;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext, Guid>()
.AddDefaultTokenProviders();
services.AddMvc();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
O AddEntityFrameworkStores método aceita um TKey tipo que indica o tipo de dados da chave primária.
Adicionar propriedades de navegação
Alterar a configuração de modelo para relações pode ser mais difícil do que fazendo outras alterações. Deve-se ter
cuidado para substituir as relações existentes em vez de criar novas relações adicionais. Em particular, a relação
alterada deve especificar o mesmo (FK) propriedade de chave estrangeira como a relação existente. Por exemplo, a
relação entre Users e UserClaims é, por padrão, especificada da seguinte maneira:
builder.Entity<TUser>(b =>
{
// Each User can have many UserClaims
b.HasMany<TUserClaim>()
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
});
A FK para essa relação é especificada como o UserClaim.UserId propriedade. HasMany e WithOne são chamados
sem argumentos para criar a relação sem propriedades de navegação.
Adicionar uma propriedade de navegação ApplicationUser que permite que associado UserClaims seja
referenciado do usuário:
O TKey para IdentityUserClaim<TKey> é o tipo especificado para o PK de usuários. Nesse caso, TKey é string
porque os padrões estão sendo usados. Ele tem não o tipo de PK para o UserClaim tipo de entidade.
Agora que existe a propriedade de navegação, ele deve ser configurado no OnModelCreating :
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
});
}
}
Observe que a relação é configurada exatamente como ele era antes, apenas com uma propriedade de navegação
especificada na chamada para HasMany .
As propriedades de navegação só existem no modelo do EF, não o banco de dados. Como a FK para a relação não
foi alterado, esse tipo de alteração de modelo não exige o banco de dados a ser atualizada. Isso pode ser verificado
com a adição de uma migração após fazer a alteração. O Up e Down métodos estão vazios.
Adicionar todas as propriedades de navegação do usuário
O exemplo a seguir usando a seção acima como orientação, configura as propriedades de navegação unidirecional
para todas as relações em usuário:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; }
}
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne()
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
}
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Each Role can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
});
}
}
Notas:
Este exemplo também inclui o UserRole entidade, que é necessário para navegar pela relação muitos-para-
muitos dos usuários às funções de ingresso.
Lembre-se de alterar os tipos das propriedades de navegação de refletir isso ApplicationXxx tipos agora estão
sendo usados em vez de IdentityXxx tipos.
Lembre-se de usar o ApplicationXxx no genérico ApplicationContext definição.
Adicionar todas as propriedades de navegação
Usando a seção acima como orientação, o exemplo a seguir configura as propriedades de navegação para todas as
relações em todos os tipos de entidade:
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne(e => e.User)
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Each Role can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
modelBuilder.Entity<IdentityUser>(b =>
{
b.ToTable("MyUsers");
});
modelBuilder.Entity<IdentityUserClaim<string>>(b =>
{
b.ToTable("MyUserClaims");
});
modelBuilder.Entity<IdentityUserLogin<string>>(b =>
{
b.ToTable("MyUserLogins");
});
modelBuilder.Entity<IdentityUserToken<string>>(b =>
{
b.ToTable("MyUserTokens");
});
modelBuilder.Entity<IdentityRole>(b =>
{
b.ToTable("MyRoles");
});
modelBuilder.Entity<IdentityRoleClaim<string>>(b =>
{
b.ToTable("MyRoleClaims");
});
modelBuilder.Entity<IdentityUserRole<string>>(b =>
{
b.ToTable("MyUserRoles");
});
}
Esses exemplos usam os tipos de identidade padrão. Se usar um tipo de aplicativo, como ApplicationUser ,
configurar esse tipo em vez do tipo padrão.
O exemplo a seguir altera alguns nomes de coluna:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>(b =>
{
b.Property(e => e.Email).HasColumnName("EMail");
});
modelBuilder.Entity<IdentityUserClaim<string>>(b =>
{
b.Property(e => e.ClaimType).HasColumnName("CType");
b.Property(e => e.ClaimValue).HasColumnName("CValue");
});
}
Alguns tipos de colunas de banco de dados podem ser configurados com determinados facetas (por exemplo, o
máximo string comprimento permitido). O exemplo a seguir define os comprimentos máximos da coluna para
várias string propriedades no modelo:
modelBuilder.Entity<IdentityUser>(b =>
{
b.Property(u => u.UserName).HasMaxLength(128);
b.Property(u => u.NormalizedUserName).HasMaxLength(128);
b.Property(u => u.Email).HasMaxLength(128);
b.Property(u => u.NormalizedEmail).HasMaxLength(128);
});
modelBuilder.Entity<IdentityUserToken<string>>(b =>
{
b.Property(t => t.LoginProvider).HasMaxLength(128);
b.Property(t => t.Name).HasMaxLength(128);
});
}
modelBuilder.HasDefaultSchema("notdbo");
}
Carregamento lento
Nesta seção, o suporte para proxies de carregamento lento no modelo de identidade é adicionado. Carregamento
lento é útil, pois ele permite que as propriedades de navegação a ser usada sem primeiro garantir que eles são
carregados.
Tipos de entidade podem ser feitos adequados para carregamento lento de diversas maneiras, conforme descrito
na documentação do EF Core. Para simplificar, use proxies de carregamento lento, que requer:
Instalação dos entityframeworkcore pacote.
Uma chamada para UseLazyLoadingProxies dentro de AddDbContext<TContext>.
Tipos de entidade pública com public virtual propriedades de navegação.
O exemplo a seguir demonstra a chamada UseLazyLoadingProxies em Startup.ConfigureServices :
services
.AddDbContext<ApplicationDbContext>(
b => b.UseSqlServer(connectionString)
.UseLazyLoadingProxies())
.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Consulte os exemplos anteriores para obter orientação sobre como adicionar propriedades de navegação para os
tipos de entidade.
Recursos adicionais
Identidade de Scaffold em projetos ASP.NET Core
Opções de autenticação de comunidade OSS para
ASP.NET Core
22/06/2018 • 2 minutes to read • Edit Online
Esta página contém as opções de autenticação fornecido pela comunidade de código aberto para o ASP.NET Core.
Esta página é atualizada periodicamente como novos provedores de se tornar disponíveis.
NOME DESCRIÇÃO
Identidade do ASP.NET Core usa valores padrão para configurações como a política de senha, bloqueio e
configuração de cookie. Essas configurações podem ser substituídas no Startup classe.
Opções de identidade
O IdentityOptions classe representa as opções que podem ser usadas para configurar o sistema de identidade.
IdentityOptions deve ser definido após chamando AddIdentity ou AddDefaultIdentity .
Bloqueio
O bloqueio é definido na PasswordSignInAsync método:
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(Input.Email,
Input.Password, Input.RememberMe,
lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl,
Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
services.Configure<IdentityOptions>(options =>
{
// Default Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
});
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. As senhas devem ter pelo menos seis caracteres. PasswordOptions podem ser
definidas na Startup.ConfigureServices .
services.Configure<IdentityOptions>(options =>
{
// Default Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
});
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;
});
Entrar
O seguinte código define SignIn configurações (para valores padrão):
services.Configure<IdentityOptions>(options =>
{
// Default SignIn settings.
options.SignIn.RequireConfirmedEmail = true;
options.SignIn.RequireConfirmedPhoneNumber = false;
});
Tokens
IdentityOptions.Tokens Especifica o TokenOptions com as propriedades mostradas na tabela.
PROPRIEDADE DESCRIÇÃO
User
services.Configure<IdentityOptions>(options =>
{
// Default User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
options.User.RequireUniqueEmail = true;
});
Configurações de cookie
Configurar o cookie do aplicativo em Startup.ConfigureServices . ConfigureApplicationCookie deve ser chamado
após chamando AddIdentity ou AddDefaultIdentity .
services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.Cookie.Name = "YourAppCookieName";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.LoginPath = "/Identity/Account/Login";
// ReturnUrlParameter requires
//using Microsoft.AspNetCore.Authentication.Cookies;
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;
});
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;
});
services.Configure<IdentityOptions>(options =>
{
// Cookie settings
options.Cookies.ApplicationCookie.CookieName = "YourAppCookieName";
options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromDays(150);
options.Cookies.ApplicationCookie.LoginPath = "/Account/LogIn";
options.Cookies.ApplicationCookie.AccessDeniedPath = "/Account/AccessDenied";
options.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
// Requires `using Microsoft.AspNetCore.Authentication.Cookies;`
options.Cookies.ApplicationCookie.AuthenticationScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.Cookies.ApplicationCookie.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
});
Ao modificar um projeto existente, confirme se o arquivo de projeto inclui uma referência de pacote para o
metapacote do Microsoft ou o Microsoft.AspNetCore.Authentication pacote do NuGet.
Quando o projeto é publicado pelo SDK (sem o <IsTransformWebConfigDisabled> propriedade definida como
true no arquivo de projeto), publicado Web. config arquivo inclui o
<location><system.webServer><security><authentication> seção. Para obter mais informações sobre o
<IsTransformWebConfigDisabled> propriedade, consulte Hospedar o ASP.NET Core no Windows com o IIS.
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="false" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
O <system.webServer> seção adicionada para o Web. config arquivo pelo Gerenciador do IIS está fora do
aplicativo <location> seção adicionada pelo SDK do .NET Core quando o aplicativo for publicado. Porque a
seção é adicionada fora das <location> nó, as configurações são herdadas por qualquer subaplicativos ao
aplicativo atual. Para impedir a herança, mova a adicionada <security> seção dentro do
<location><system.webServer> seção que o SDK fornecido.
Quando o Gerenciador do IIS é usado para adicionar a configuração do IIS, ele afeta somente o aplicativo
Web. config arquivo no servidor. Uma implantação subsequente do aplicativo pode substituir as
configurações no servidor se a cópia do servidor do Web. config é substituído do projeto Web. config arquivo.
Use qualquer das abordagens a seguir para gerenciar as configurações:
Use o Gerenciador do IIS para redefinir as configurações na Web. config depois que o arquivo será
substituído na implantação de arquivos.
Adicionar um arquivo Web. config para o aplicativo localmente com as configurações. Para obter mais
informações, consulte o configuração do lado do desenvolvimento seção.
Publicar e implantar seu projeto para a pasta de site do IIS
Usando o Visual Studio ou a CLI do .NET Core, publicar e implantar o aplicativo para a pasta de destino.
Para obter mais informações sobre hospedagem com o IIS, publicação e implantação, consulte os tópicos a
seguir:
dotnet publish
Hospedar o ASP.NET Core no Windows com o IIS
Módulo do ASP.NET Core
Perfis de publicação do Visual Studio para a implantação do aplicativo ASP.NET Core
Inicie o aplicativo para verificar se a autenticação do Windows está funcionando.
NOTE
O HTTP.sys delega à autenticação de modo kernel com o protocolo de autenticação Kerberos. Não há suporte para
autenticação de modo de usuário com o Kerberos e o HTTP.sys. A conta do computador precisa ser usada para
descriptografar o token/tíquete do Kerberos que é obtido do Active Directory e encaminhado pelo cliente ao servidor
para autenticar o usuário. Registre o SPN (nome da entidade de serviço) do host, não do usuário do aplicativo.
NOTE
Não há suporte para http. sys no Nano Server versão 1709 ou posterior. Para usar a autenticação do Windows e o
HTTP. sys com o Nano Server, use uma contêiner de Server Core (microsoft/windowsservercore). Para obter mais
informações sobre o Server Core, consulte qual é a opção de instalação Server Core no Windows Server?.
NOTE
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 pode ser configurado para fornecer aos usuários uma melhor experiência de
"Acesso negado".
IIS
Se usar o IIS, adicione o seguinte para o ConfigureServices método:
HTTP.sys
Se usando HTTP. sys, adicione o seguinte para o ConfigureServices método:
Representação
ASP.NET Core não implementar a representação. Aplicativos executados com a identidade do aplicativo para
todas as solicitações, usando a identidade de pool ou processo do aplicativo. Se você precisar executar
explicitamente uma ação em nome do usuário, use WindowsIdentity.RunImpersonated em um middleware
embutido terminal em Startup.Configure . Executar uma única ação nesse 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}\t" +
$"State: {impersonatedUser.ImpersonationLevel}";
RunImpersonated não oferece suporte a operações assíncronas e não deve ser usado para cenários complexos.
Por exemplo, solicitações de todas ou cadeias de middleware de encapsulamento não é tem suporte ou
recomendado.
Provedores de armazenamento personalizados para
ASP.NET Core Identity
26/10/2018 • 20 minutes 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, você pode preferir usar um mecanismo de persistência diferente ou esquema de dados. Por exemplo:
Você usa Azure Table Storage ou outro armazenamento de dados.
As tabelas de banco de dados têm uma estrutura diferente.
Talvez você queira usar uma abordagem de acesso de dados diferentes, como Dapper.
Em cada um desses casos, você pode escrever um provedor personalizado para seu mecanismo de
armazenamento e conecte esse provedor ao seu aplicativo.
O ASP.NET Core Identity é incluído nos modelos de projeto no Visual Studio com a opção "Contas de usuário
individuais".
Ao usar a CLI do .NET Core, adicione -au Individual :
if(rows > 0)
{
return IdentityResult.Success;
}
return IdentityResult.Failed(new IdentityError { Description = $"Could not insert user {user.Email}." });
}
Interfaces a serem implementadas durante a personalização armazenamento de usuário
IUserStore
O IUserStore<TUser> interface é a única interface, você deve implementar no repositório do usuário. Define
métodos para criar, atualizar, excluir e recuperar os usuários.
IUserClaimStore
O IUserClaimStore<TUser> interface define os métodos a implementar para habilitar declarações do usuário.
Ela contém métodos para adicionar, remover e recuperando as declarações de usuário.
IUserLoginStore
O IUserLoginStore<TUser> define os métodos a implementar para habilitar provedores de autenticação
externa. Ela contém métodos para adicionar, remover e recuperar os logons de usuário e um método para
recuperar um usuário com base nas informações de logon.
IUserRoleStore
O IUserRoleStore<TUser> interface define os métodos a implementar para mapear um usuário a uma função.
Ela contém métodos para adicionar, remover e recuperar funções de usuário e um método para verificar se um
usuário está atribuído a uma função.
IUserPasswordStore
O IUserPasswordStore<TUser> interface define os métodos a implementar para persistir as senhas hash. Ela
contém métodos para obter e definir a senha de hash e um método que indica se o usuário tiver definido uma
senha.
IUserSecurityStampStore
O IUserSecurityStampStore<TUser> interface define os métodos a implementar para usar um carimbo de
segurança para que indica se as informações da conta do usuário foi alterado. Esse carimbo é atualizado
quando um usuário altera a senha ou adiciona ou remove logons. Ela contém métodos para obter e definir o
carimbo de segurança.
IUserTwoFactorStore
O IUserTwoFactorStore<TUser> interface define os métodos a implementar para dar suporte à autenticação de
dois fatores. Ela contém métodos para obter e definir se a autenticação de dois fatores é ativada para um
usuário.
IUserPhoneNumberStore
O IUserPhoneNumberStore<TUser> interface define os métodos a implementar para armazenar números de
telefone do usuário. Ela contém métodos para obter e definir o número de telefone e se o número de telefone
foi confirmado.
IUserEmailStore
O IUserEmailStore<TUser> interface define os métodos a implementar para armazenar endereços de email do
usuário. Ela contém métodos para obter e definir o endereço de email e se o email for confirmado.
IUserLockoutStore
O IUserLockoutStore<TUser> interface define os métodos a implementar para armazenar informações sobre
como bloquear uma conta. Ela contém métodos para controlar as tentativas de acesso com falha e bloqueios.
IQueryableUserStore
O IQueryableUserStore<TUser> interface define os membros que você pode implementar para fornecer um
repositório de usuários que podem ser consultados.
Você pode implementar apenas as interfaces que são necessários em seu aplicativo. Por exemplo:
public class UserStore : IUserStore<IdentityUser>,
IUserClaimStore<IdentityUser>,
IUserLoginStore<IdentityUser>,
IUserRoleStore<IdentityUser>,
IUserPasswordStore<IdentityUser>,
IUserSecurityStampStore<IdentityUser>
{
// interface implementations not shown
}
using System;
namespace CustomIdentityProviderSample.CustomProvider
{
public class ApplicationRole
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; }
}
}
IRoleStore<TRole>
O IRoleStore<TRole> interface define os métodos a implementar na classe de repositório de função. Ela
contém métodos para criar, atualizar, excluir e recuperar funções.
RoleStore<TRole>
Para personalizar RoleStore , crie uma classe que implementa o IRoleStore<TRole> interface.
// 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 personalizados para a identidade do ASP.NET 4.x
ASP.NET Core Identity – neste repositório inclui links para a comunidade mantida provedores de
armazenamento.
Autenticação de Facebook, Google e de provedor
externo no ASP.NET Core
21/01/2019 • 6 minutes 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.
Aplicar migrações
Execute o aplicativo e selecione o link Registrar.
Insira o email e a senha para a nova conta e, em seguida, selecione Registrar.
Siga as instruções para aplicar as migrações.
IMPORTANT
O Secret Manager destina-se apenas para fins de desenvolvimento. Você pode armazenar e proteger os segredos de
teste e produção do Azure com o provedor de configuração do Azure Key Vault.
Siga as etapas no tópico Armazenamento seguro dos segredos do aplicativo em desenvolvimento no ASP.NET
Core para armazenar os tokens atribuídos por cada provedor de logon abaixo.
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
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.
Você talvez queira manter os dados adicionais sobre o usuário e seus tokens de atualização e acesso.
Para obter mais informações, consulte Manter declarações adicionais e os tokens de provedores
externos no ASP.NET Core.
Configuração de logon externo do Google no
ASP.NET Core
18/01/2019 • 7 minutes to read • Edit Online
AuthenticationBuilder métodos de extensão que registra um manipulador de autenticação podem ser chamados
apenas uma vez por esquema de autenticação. Há sobrecargas que permitem configurar as propriedades de
esquema, o nome de esquema e nome de exibição.
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Consulte a GoogleOptions referência da API para obter mais informações sobre opções de configuração com
suporte pela autenticação do Google. Isso pode ser usado para solicitar informações diferentes sobre o usuário.
Solução de problemas
Se a entrada não funciona e você não estiver recebendo erros, alterne para modo de desenvolvimento para
que o problema mais fácil de depurar.
Se a identidade não estiver configurada, chamando services.AddIdentity na ConfigureServices , tentar
autenticar resulta 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 foi criado por meio da aplicação a migração inicial, você obterá uma
operação de banco de dados falhou ao processar a solicitação erro. Toque aplicar migrações para criar o
banco de dados e atualizar 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 na página anterior.
Depois que você publica o aplicativo no Azure, redefinir o ClientSecret no Console de API do Google.
Defina as Authentication:Google:ClientId e Authentication:Google:ClientSecret como configurações de
aplicativo no portal do Azure. Configurar o sistema de configuração para ler as chaves de variáveis de
ambiente.
Configuração de logon externo do Facebook no
ASP.NET Core
27/12/2018 • 9 minutes to read • Edit Online
NOTE
O URI /signin-facebook é definido como o retorno de chamada padrão do provedor de autenticação do Facebook. Você
pode alterar o retorno de chamada padrão URI ao configurar o middleware de autenticação do Facebook por meio de
herdadas RemoteAuthenticationOptions.CallbackPath propriedade da FacebookOptions classe.
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
AuthenticationBuilder métodos de extensão que registra um manipulador de autenticação podem ser chamados
apenas uma vez por esquema de autenticação. Há sobrecargas que permitem configurar as propriedades de
esquema, o nome de esquema e nome de exibição.
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Consulte as referências de API do FacebookOptions para obter mais informações sobre as opções de
configuração compatíveis com a autenticação do Facebook. As opções de configuração podem ser usadas para:
Solicitar informações diferentes sobre o usuário.
Adicionar argumentos de cadeia de caracteres de consulta para personalizar a experiência de logon.
Quando você clica em Facebook, você será redirecionado ao Facebook para autenticação:
Autenticação do Facebook solicita o endereço de email e o perfil público por padrão:
Depois que você insira suas credenciais do Facebook, você será redirecionado para seu site em que você pode
definir seu email.
Agora você está conectado usando suas credenciais do Facebook:
Solução de problemas
ASP.NET Core 2.x somente: Se a identidade não está configurada por meio da chamada
services.AddIdentity na ConfigureServices , a tentativa de autenticar 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 foi criado por meio da aplicação a migração inicial, você obterá uma
operação de banco de dados falhou ao processar a solicitação erro. Toque aplicar migrações para criar o
banco de dados e atualizar para continuar após o erro.
Próximas etapas
Adicione a Microsoft.AspNetCore.Authentication.Facebook pacote NuGet ao seu projeto para cenários
avançados de autenticação de Facebook. Este pacote não é necessário para integrar funcionalidade de
logon externo do Facebook com seu aplicativo.
Este artigo mostrou como você pode autenticar com o Facebook. Você pode seguir uma abordagem
semelhante para autenticar com outros provedores listados na página anterior.
Depois de publicar seu site da web para aplicativo web do Azure, você deve redefinir o AppSecret no
portal do desenvolvedor do Facebook.
Defina as Authentication:Facebook:AppId e Authentication:Facebook:AppSecret como configurações de
aplicativo no portal do Azure. Configurar o sistema de configuração para ler as chaves de variáveis de
ambiente.
Configuração de logon externo Account da
Microsoft com o ASP.NET Core
27/12/2018 • 10 minutes to read • Edit Online
Se você ainda não tiver uma conta da Microsoft, toque em crie uma! Depois de entrar, você será redirecionado
para meus aplicativos página:
Toque adicionar um aplicativo no canto superior direito de canto e insira seu nome do aplicativo e
Contact Email:
NOTE
O segmento URI /signin-microsoft é definido como o retorno de chamada padrão do provedor de autenticação do
Microsoft. Você pode alterar o retorno de chamada padrão URI ao configurar o middleware de autenticação da Microsoft
por meio de herdadas RemoteAuthenticationOptions.CallbackPath propriedade do MicrosoftAccountOptions classe.
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ApplicationId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:Password"];
});
AuthenticationBuilder métodos de extensão que registra um manipulador de autenticação podem ser chamados
apenas uma vez por esquema de autenticação. Há sobrecargas que permitem configurar as propriedades de
esquema, o nome de esquema e nome de exibição.
app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions()
{
ClientId = Configuration["Authentication:Microsoft:ApplicationId"],
ClientSecret = Configuration["Authentication:Microsoft:Password"]
});
Embora a terminologia usada no Portal do desenvolvedor Microsoft nomeia esses tokens ApplicationId e
Password , elas são expostas como ClientId e ClientSecret para a API de configuração.
Consulte a MicrosoftAccountOptions referência da 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.
Quando você clica na Microsoft, você será redirecionado para a Microsoft para autenticação. Após entrar com
sua Account da Microsoft (se ainda não estiver conectado), você será solicitado para permitir que o aplicativo
acessar suas informações:
Toque 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:
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 na página anterior.
Depois de publicar seu site da web para aplicativo web do Azure, você deve criar um novo Password no
Portal do desenvolvedor Microsoft.
Defina as Authentication:Microsoft:ApplicationId e Authentication:Microsoft:Password como
configurações de aplicativo no portal do Azure. Configurar o sistema de configuração para ler as chaves
de variáveis de ambiente.
Configuração de logon externo do Twitter com o
ASP.NET Core
27/12/2018 • 8 minutes to read • Edit Online
Toque criar novo aplicativo e preencha o requerimento nome, descrição e pública site URI (Isso pode
ser temporário até que você Registre o nome de domínio):
Insira o URI de desenvolvimento com /signin-twitter acrescentados em de URIs de redirecionamento
de OAuth válido campo (por exemplo: https://localhost:44320/signin-twitter ). O esquema de
autenticação do Twitter configurado mais tarde neste tutorial automaticamente manipulará as solicitações
em /signin-twitter rota para implementar o fluxo de OAuth.
NOTE
O segmento URI /signin-twitter é definido como o retorno de chamada padrão do provedor de autenticação
do Twitter. Você pode alterar o retorno de chamada padrão URI ao configurar o middleware de autenticação do
Twitter por meio de herdadas RemoteAuthenticationOptions.CallbackPath propriedade da TwitterOptions classe.
Preencha o restante do formulário e toque criar seu aplicativo Twitter. Novos detalhes do aplicativo são
exibidos:
Ao implantar o site será necessário rever a gerenciamento de aplicativos página e registrar um novo
URI público.
Esses tokens podem ser encontrados na chaves e Tokens de acesso guia depois de criar seu novo aplicativo do
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.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication().AddTwitter(twitterOptions =>
{
twitterOptions.ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"];
twitterOptions.ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"];
});
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
app.UseTwitterAuthentication(new TwitterOptions()
{
ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"],
ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"]
});
Consulte a TwitterOptions referência da API para obter mais informações sobre opções de configuração com
suporte pela autenticação do Twitter. Isso pode ser usado para solicitar informações diferentes sobre o usuário.
Solução de problemas
ASP.NET Core 2.x somente: Se a identidade não está configurada por meio da chamada
services.AddIdentity na ConfigureServices , a tentativa de autenticar 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-se a migração inicial, você obterá uma operação
de banco de dados falhou ao processar a solicitação erro. Toque aplicar migrações para criar o banco de
dados e atualizar 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 na página anterior.
Depois de publicar seu site da web para aplicativo web do Azure, você deve redefinir o ConsumerSecret no
portal do desenvolvedor do Twitter.
Defina as Authentication:Twitter:ConsumerKey e Authentication:Twitter:ConsumerSecret como
configurações de aplicativo no portal do Azure. Configurar o sistema de configuração para ler as chaves de
variáveis de ambiente.
Provedores de autenticação OAuth externos
16/11/2018 • 2 minutes to read • Edit Online
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Pré-requisitos
Decida quais provedores de autenticação externa para dar suporte a no aplicativo. Para cada provedor, registrar o
aplicativo e obter o ID do cliente e segredo do cliente. Para obter mais informações, consulte Autenticação de
Facebook, Google e de provedor externo no ASP.NET Core. O aplicativo de exemplo usa o provedor de autenticação
do Google.
PROVIDER ESCOPO
Facebook https://www.facebook.com/dialog/oauth
Google https://www.googleapis.com/auth/plus.login
Microsoft https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitter https://api.twitter.com/oauth/authenticate
O aplicativo de exemplo adiciona o Google plus.login escopo para solicitar a entrada do Google + nas permissões:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXX.apps.googleusercontent.com";
// Provide the Google Secret
options.ClientSecret = "g4GZ2#...GD5Gg1x";
options.Scope.Add("https://www.googleapis.com/auth/plus.login");
options.ClaimActions.MapJsonKey(ClaimTypes.Gender, "gender");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens()
as List<AuthenticationToken>;
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXX.apps.googleusercontent.com";
// Provide the Google Secret
options.ClientSecret = "g4GZ2#...GD5Gg1x";
options.Scope.Add("https://www.googleapis.com/auth/plus.login");
options.ClaimActions.MapJsonKey(ClaimTypes.Gender, "gender");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens()
as List<AuthenticationToken>;
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
if (info == null)
{
throw new ApplicationException(
"Error loading external login data during confirmation.");
}
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// Copy over the gender claim
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.Gender));
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
}
ReturnUrl = returnUrl;
return Page();
}
Account/ExternalLogin.cshtml.cs:
public async Task<IActionResult> OnPostConfirmationAsync(
string returnUrl = null)
{
if (ModelState.IsValid)
{
// Get the information about the user from the external login
// provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
throw new ApplicationException(
"Error loading external login data during confirmation.");
}
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// Copy over the gender claim
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.Gender));
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
}
ReturnUrl = returnUrl;
return Page();
}
public async Task<IActionResult> OnGetCallbackAsync(
string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login");
}
if (info == null)
{
return RedirectToPage("./Login");
}
// Sign in the user with this external login provider if the user
// already has a login
var result = await _signInManager.ExternalLoginSignInAsync(
info.LoginProvider, info.ProviderKey, isPersistent: false,
bypassTwoFactor : true);
if (result.Succeeded)
{
// Store the access token and resign in so the token is included in
// in the cookie
var user = await _userManager.FindByLoginAsync(info.LoginProvider,
info.ProviderKey);
_logger.LogInformation(
"{Name} logged in with {LoginProvider} provider.",
info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to
// create an account
ReturnUrl = returnUrl;
LoginProvider = info.LoginProvider;
return Page();
}
}
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXX.apps.googleusercontent.com";
// Provide the Google Secret
options.ClientSecret = "g4GZ2#...GD5Gg1x";
options.Scope.Add("https://www.googleapis.com/auth/plus.login");
options.ClaimActions.MapJsonKey(ClaimTypes.Gender, "gender");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens()
as List<AuthenticationToken>;
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
b36a7b09-9135-4810-b7a5-78697ff23e99
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
[email protected]
AspNet.Identity.SecurityStamp
29G2TB881ATCUQFJSRFG1S0QJ0OOAWVT
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender
female
http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod
Google
Authentication Properties
.Token.access_token
bv42.Dgw...GQMv9ArLPs
.Token.token_type
Bearer
.Token.expires_at
2018-08-27T19:08:00.0000000+00:00
.Token.TicketCreated
8/27/2018 6:08:00 PM
.TokenNames
access_token;token_type;expires_at;TicketCreated
.issued
Mon, 27 Aug 2018 18:08:05 GMT
.expires
Mon, 10 Sep 2018 18:08:05 GMT
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 Azure Active Directory (AAD ). Ele usa o aplicativo de exemplo
do ASP.NET Core 2.0 descrito em Facebook, Google e a autenticação de provedor externo.
Para aplicativos ASP.NET Core 2.0, o suporte do WS -Federation é fornecido pela
Microsoft.AspNetCore.Authentication.WsFederation. Esse componente foi transferido da
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 de XSRF.
No entanto, ele pode ser habilitado 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 assume como padrão /signin-wsfed mas
pode ser alterado por meio de herdado RemoteAuthenticationOptions.CallbackPath propriedade do
WsFederationOptions classe. Esse caminho pode ser compartilhado com outros provedores de autenticação,
permitindo que o SkipUnrecognizedRequests opção.
Insira um nome de 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 configure 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:
NOTE
Isso deve ser uma URL HTTPS. O IIS Express pode fornecer um certificado autoassinado, ao hospedar o aplicativo durante o
desenvolvimento. O kestrel requer configuração manual do certificado. Consulte a documentação do 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 que o aplicativo escuta como o URL de logon:
Clique em pontos de extremidade e observe o documento de metadados de Federação URL. Esse é o
middleware de Web Services Federation MetadataAddress :
Navegue até o novo registro do aplicativo. Clique em as configurações > as propriedades e anote o URI da
ID do aplicativo. Esse é o middleware de Web Services Federation Wtrealm :
Adicionar o WS-Federation como um provedor de logon externo para
ASP.NET Core Identity
Adicionar uma dependência no Microsoft.AspNetCore.Authentication.WsFederation ao projeto.
Adicionar o WS -Federation para Startup.ConfigureServices :
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 ADFS como o provedor, o botão redireciona para uma página de entrada do AD FS:
Com o Azure Active Directory como o provedor, o botão redireciona para uma página de logon do AAD:
Um entrar com êxito para um novo usuário redireciona para a página de registro de usuário do aplicativo:
Ver esse arquivo PDF para o ASP.NET Core 1.1 e a versão 2.1.
Por Rick Anderson e Joe Audette
Este tutorial mostra como criar um aplicativo ASP.NET Core com a redefinição de senha e de confirmação de
email. Este tutorial é não um tópico de início. Você deve estar familiarizado com:
ASP.NET Core
Autenticação
Entity Framework Core
Pré-requisitos
SDK do .NET Core 2.1 ou posteriores
services.AddDefaultIdentity<IdentityUser>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<WebPWrecoverContext>();
});
}
}
config.SignIn.RequireConfirmedEmail = true; impede que os usuários registrados entrar até que o email seja
confirmado.
Configurar o provedor de email
Neste tutorial SendGrid é usado para enviar email. Você precisa de uma conta do SendGrid e uma chave para
enviar email. Você pode usar outros provedores de email. ASP.NET Core 2.x inclui System.Net.Mail , que permite
que você enviar um email de seu aplicativo. É recomendável que usar o SendGrid ou outro serviço de email para
enviar email. SMTP é difícil proteger e configurado corretamente.
Crie uma classe para buscar a chave de email seguro. Para este exemplo, crie
Services/AuthMessageSenderOptions.cs:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<UserSecretsId>aspnet-WebPWrecover-1234</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.1" />
<PackageReference Include="SendGrid" Version="9.9.0" />
</ItemGroup>
</Project>
O conteúdo a Secrets arquivo não são criptografadas. O Secrets arquivo é mostrado abaixo (o SendGridKey valor
tiver sido removido.)
{
"SendGridUser": "RickAndMSFT",
"SendGridKey": "<key removed>"
}
Install-Package SendGrid
Ver inicie gratuitamente com o SendGrid para se registrar para uma conta gratuita do SendGrid.
Implementar IEmailSender
Para implementar IEmailSender , crie Services/EmailSender.cs com um código semelhante ao seguinte:
using Microsoft.AspNetCore.Identity.UI.Services;
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("[email protected]", "Joe Smith"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
return client.SendEmailAsync(msg);
}
}
}
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// requires
// using Microsoft.AspNetCore.Identity.UI.Services;
// using WebPWrecover.Services;
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
}
Talvez seja necessário expandir a barra de navegação para ver o nome de usuário.
A página Gerenciar é exibida com o perfil guia selecionada. O Email mostra uma caixa de seleção que indica o
email foi confirmada.
Teste a redefinição de senha
Se você estiver conectado, selecione Logout.
Selecione o login link e selecione o esqueceu sua senha? link.
Insira o email usado para registrar a conta.
Um email com um link para redefinir sua senha é enviado. Verifique seu email e clique no link para redefinir
sua senha. Depois que sua senha foi redefinida com êxito, você pode entrar com seu email e a nova senha.
Depurar o email
Se você não é possível obter o trabalho de email:
Defina um ponto de interrupção no EmailSender.Execute para verificar SendGridClient.SendEmailAsync é
chamado.
Criar uma aplicativo de console para enviar email usando um código semelhante ao EmailSender.Execute .
Examine os atividade de Email página.
Verifique sua pasta de spam.
Tente outro alias de email em outro provedor de email (Microsoft, Yahoo, Gmail, etc.)
Tente enviar para contas de email diferente.
Uma prática recomendada de segurança é não use segredos de produção em desenvolvimento e teste. Se
você publicar o aplicativo no Azure, você pode definir os segredos do SendGrid como configurações de aplicativo
no portal do aplicativo Web do Azure. Configurar o sistema de configuração para ler as chaves de variáveis de
ambiente.
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 entrar com qualquer uma das contas. Convém que os usuários
adicionem contas locais no caso de seu serviço de autenticação de logon social está inoperante ou, mais
provavelmente eles tiver perdido o acesso à sua conta social.
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
Atualizar o 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 do nome da solução. Ele pode
ser alterado para qualquer valor, mas ele sempre deve ser codificado por URL.
WARNING
Dois aplicativos de autenticador 2FA (autenticação) fator, usando uma baseada em tempo avulso senha algoritmo TOTP (),
são o setor de abordagem 2fa recomendado. 2FA usar TOTP é preferencial para SMS 2FA. Para obter mais informações,
consulte geração de código de QR habilitar TOTP para aplicativos de autenticador no ASP.NET Core para ASP.NET Core 2.0 e
versões posteriores.
Este tutorial mostra como configurar a autenticação de dois fatores (2FA) usando o SMS. As instruções são
fornecidas para twilio e ASPSMS, mas você pode usar qualquer outro provedor SMS. Recomendamos que você
conclua confirmação de conta e recuperação de senha antes de iniciar este tutorial.
Exibir ou baixar o código de exemplo. Como baixar.
Adicione código a Services/MessageServices.cs arquivo para habilitar o SMS. Use o Twilio ou seção ASPSMS:
Twilio: [!code-csharp]
ASPSMS: [!code-csharp]
Configurar a inicialização para usar SMSoptions
Adicionar um número de telefone que receberá o código de verificação e toque enviar 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 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
Se o aplicativo não usa o metapacote do Microsoft, criar uma referência de pacote no arquivo de projeto para o
Microsoft.AspNetCore.Authentication.Cookies pacote (versão 2.1.0 ou mais tarde).
No ConfigureServices método, crie 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 =>
{
...
});
ASP.NET Core 1.x usa cookie middleware que serializa uma entidade de usuário em um cookie criptografado. Em
solicitações subsequentes, o cookie é validado, e a entidade de segurança é recriada e atribuída ao
HttpContext.User propriedade.
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AccessDeniedPath = "/Account/Forbidden/",
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
LoginPath = "/Account/Unauthorized/"
});
Opções de CookieAuthenticationOptions
O CookieAuthenticationOptions classe é usada para configurar as opções de provedor de autenticação.
OPÇÃO DESCRIÇÃO
OPÇÃO DESCRIÇÃO
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
});
app.UseCookiePolicy(cookiePolicyOptions);
PROPRIEDADE DESCRIÇÃO
A configuração de Middleware de política de Cookie para MinimumSameSitePolicy pode afetar sua configuração de
Cookie.SameSite em CookieAuthenticationOptions configurações de acordo com a matriz a seguir.
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);
await HttpContext.Authentication.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
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 usada é ASP.NET Core proteção de dados sistema. Se você estiver hospedando o
aplicativo em várias máquinas, balanceamento de carga entre aplicativos ou usar uma web farm, você deve
configurar a proteção de dados para usar o mesmo anel de chave e o identificador do aplicativo.
Sair
Para desconectar o usuário atual e excluir seus cookies, chame SignOutAsync:
await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
Para implementar uma substituição para o ValidatePrincipal eventos, escreva um método com a assinatura a
seguir em uma classe que você deriva CookieAuthenticationEvents:
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. Fornecer
um registro de serviço com escopo para sua CustomCookieAuthenticationEvents classe:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
services.AddScoped<CustomCookieAuthenticationEvents>();
Para implementar uma substituição para o ValidateAsync eventos, escreva um método com a seguinte assinatura:
ValidateAsync(CookieValidatePrincipalContext)
Identidade do ASP.NET Core implementa essa verificação como parte de sua SecurityStampValidator. Um
exemplo é semelhante ao seguinte:
public static class LastChangedValidator
{
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
// Pull database from registered DI services.
var userRepository =
context.HttpContext.RequestServices
.GetRequiredService<IUserRepository>();
var userPrincipal = context.Principal;
if (string.IsNullOrEmpty(lastChanged) ||
!userRepository.ValidateLastChanged(lastChanged))
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = LastChangedValidator.ValidateAsync
}
});
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 forma não destrutiva a entidade de usuário, chame
context.ReplacePrincipal e defina o context.ShouldRenew propriedade true .
WARNING
A abordagem descrita aqui é disparada em cada solicitação. Isso pode resultar em uma penalidade de desempenho grande
para o aplicativo.
Cookies persistentes
Talvez você queira que o cookie para persistir entre as sessões do navegador. Essa persistência deve ser habilitada
apenas com o consentimento explícito do usuário com uma caixa de seleção "Lembrar-Me" no logon ou um
mecanismo semelhante.
O trecho de código a seguir cria uma identidade e o cookie correspondente que sobrevive a por meio de
fechamentos de navegador. Quaisquer configurações de expiração deslizante configuradas anteriormente são
consideradas. Se o cookie expirar enquanto o navegador é fechado, o navegador limpa o cookie depois que ele
seja reiniciado.
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true
});
await HttpContext.Authentication.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true
});
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
});
await HttpContext.Authentication.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
});
Recursos adicionais
As alterações do AUTH 2.0 / migração comunicado
Autorizar com um esquema específico no ASP.NET Core
Autorização baseada em declarações no núcleo do ASP.NET
Verificações de função baseado em políticas
Hospedar o ASP.NET Core em um web farm
Azure Active Directory com o ASP.NET Core
10/12/2018 • 2 minutes to read • Edit Online
TIP
Azure Active Directory (Azure AD) e o Azure AD B2C são ofertas de produtos separados. Um locatário do AD do Azure
representa uma organização, enquanto que um locatário do Azure AD B2C representa uma coleção de identidades a serem
usados 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 2017 (qualquer edição)
WARNING
Se estar ciente de como configurar uma URL de resposta não sejam localhost, o restrições sobre o que é permitido na lista de
URL de resposta.
Depois que o aplicativo é registrado, é exibida a lista de aplicativos no locatário. Selecione o aplicativo que acabou
de registrar. Selecione o cópia ícone à direita do ID do aplicativo campo para copiá-lo na área de transferência.
Nada mais podem ser configurado no locatário do Azure AD B2C neste momento, mas deixe a janela do
navegador aberta. Não 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 de resposta 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.
TIP
Se você não copiar a URL de resposta, use o endereço HTTPS na guia Debug nas propriedades do projeto da web e
acrescente o CallbackPath o valor da appSettings. JSON.
Configurar políticas
Use as etapas na documentação do Azure AD B2C para criar uma política de inscrição ou entradae então criar uma
política de redefinição de senha. Use os valores de exemplo fornecidos na documentação do provedores de
identidade, atributos de inscrição, e declarações do aplicativo. Usando o executar agora botão para testar
as políticas, conforme descrito na documentação é opcional.
WARNING
Verifique se os nomes de política são exatamente como descrito na documentação, como essas políticas foram usadas na
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 for iniciado,
selecione Accept para aceitar o uso de cookies (se solicitado) e, em seguida, selecione entrar.
O navegador é redirecionado para o locatário do Azure AD B2C. Entrar com uma conta existente (se uma foi
criada a testar as políticas) ou selecione Inscreva-se agora para criar uma nova conta. O esqueceu sua senha?
link é usado para redefinir uma senha esquecida.
Depois de entrar com êxito, o navegador é redirecionado para o aplicativo web.
Próximas etapas
Neste tutorial, você aprendeu como:
Criar um locatário do Azure Active Directory B2C
Registrar um aplicativo no Azure B2C do AD
Usar o Visual Studio para criar um aplicativo Web do ASP.NET Core configurados para usar o locatário do
Azure AD B2C para autenticação
Configurar políticas para controlar o comportamento do locatário do Azure AD B2C
Agora que o aplicativo ASP.NET Core está configurado para usar o Azure AD B2C para autenticação, o atributo
Authorize pode ser usado para proteger seu aplicativo. Continue a desenvolver seu aplicativo aprendendo a:
Personalizar a interface do usuário do Azure AD B2C.
Configurar os requisitos de complexidade de senha.
Habilitar a autenticação multifator.
Configurar provedores de identidade adicional, como Microsoft, Facebook, Google, Amazon, do Twitter e
outros.
Usar a API do Graph do Azure AD para recuperar informações de usuário adicionais, como associação de
grupo, do locatário do Azure AD B2C.
Proteger um ASP.NET Core API da web usando o Azure AD B2C.
Chamar uma API web de um aplicativo web do .NET usando o Azure AD B2C.
Autenticação em APIs web com o Azure Active
Directory B2C no ASP.NET Core
08/01/2019 • 17 minutes to read • Edit Online
Pré-requisitos
A seguir é necessários para este passo a passo:
Assinatura do Microsoft Azure
Visual Studio 2017 (qualquer edição)
Postman
Depois que a API é registrada, é exibida a lista de aplicativos e APIs no locatário. Selecione a API que foi
registrada anteriormente. Selecione o cópia ícone à direita do ID do aplicativo campo para copiá-lo na área de
transferência. Selecione escopos publicados 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 é decorada com o atributo Authorize, portanto, todas as solicitações
requerem autenticação.
Executar a API da web
No Visual Studio, execute a API. O Visual Studio inicia um navegador apontado na URL da raiz da API. Anote a
URL na barra de endereços e deixar a API em execução em segundo plano.
NOTE
Como não há nenhum controlador definido para a URL da raiz, o navegador pode exibir um erro 404 de (página não
encontrada). Esse comportamento é esperado.
Nome Postman
O aplicativo web registrado recentemente precisa de permissão para acessar a API da web em nome do usuário.
1. Selecione Postman na lista de aplicativos e selecione acesso à API no menu à esquerda.
2. Selecione + adicionar.
3. No selecionar API lista suspensa, selecione o nome da API web.
4. No selecionar escopos lista suspensa, verifique se todos os escopos são selecionados.
5. Selecione Okey.
Observe a ID do aplicativo do aplicativo Postman, pois ele é necessário para obter um token de portador.
Criar uma solicitação do Postman
Inicie o Postman. Por padrão, o Postman exibe a criar novo caixa de diálogo após a inicialização. Se a caixa de
diálogo não for exibida, selecione a + novo botão no canto superior esquerdo.
Dos criar novo caixa de diálogo:
1. Selecione solicitar.
2. Insira obter valores na nome da solicitação caixa.
3. Selecione + Criar coleção para criar uma nova coleção para armazenar a solicitação. Nomear a coleção
tutoriais do ASP.NET Core e, em seguida, selecione a marca de seleção.
4. Selecione o salvar em 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 da solicitação , digite a URL para ValuesController . A URL é o mesmo exibido no
navegador com api/valores acrescentado. Por exemplo, https://localhost:44375/api/values .
2. Selecione o enviar botão.
3. Observe o status da resposta é 401 não autorizado.
IMPORTANT
Se você receber um erro "Não foi possível obter qualquer resposta", você talvez precise desabilitar a verificação do certificado
SSL na configurações do Postman.
NOTE
† A caixa de diálogo de configurações de política no portal do Azure Active Directory B2C exibe duas URLs possíveis:
Uma no formato https://login.microsoftonline.com/ {nome de domínio do locatário} / {informações adicionais
de caminho} e o outro no formato https://{tenant name}.b2clogin.com/ {nome de domínio do locatário} /
{informações adicionais de caminho}. Ele tem críticos que o domínio encontrado na AzureAdB2C.Instance da API
web appSettings. JSON arquivo corresponde ao usado no aplicativo de web appSettings. JSON arquivo. Isso é o
mesmo domínio usado para o campo de URL do Auth no Postman. Observe que o Visual Studio usa um formato de
URL ligeiramente diferente que o que é exibido no portal. Desde que os domínios corresponderem, a URL funciona.
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 AD do Azure.
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 para controlar o comportamento do locatário do Azure AD B2C.
Usar o Postman para simular um aplicativo web que apresenta uma caixa de diálogo de logon, recupera um
token e usa-o para fazer uma solicitação em relação a API da web.
Continue a desenvolver sua API pelo learning para:
Proteger um ASP.NET Core em aplicativo web usando o Azure AD B2C.
Chamar uma API web de um aplicativo web do .NET usando o Azure AD B2C.
Personalizar a interface do usuário do Azure AD B2C.
Configurar os requisitos de complexidade de senha.
Habilitar a autenticação multifator.
Configurar provedores de identidade adicional, como Microsoft, Facebook, Google, Amazon, do Twitter e
outros.
Usar a API do Graph do Azure AD para recuperar informações de usuário adicionais, como associação de
grupo, do locatário do Azure AD B2C.
Artigos baseados em projetos do ASP.NET Core
criados com contas de usuário individuais
21/09/2018 • 2 minutes to read • Edit Online
O ASP.NET Core Identity é incluído nos modelos de projeto no Visual Studio com a opção "Contas de usuário
individuais".
Os modelos de autenticação estão disponíveis na CLI do .NET Core com -au Individual :
Sem autenticação
Autenticação é especificada na CLI do .NET Core com o -au opção. No Visual Studio, o alterar autenticação
caixa de diálogo está disponível para novos aplicativos da web. É o padrão para novos aplicativos web no Visual
Studio sem autenticação.
Projetos criados com nenhuma autenticação:
Não contêm páginas da web e a interface do usuário para entrar e sair.
Não contêm o código de autenticação.
Autenticação do Windows
Autenticação do Windows é especificada para novos aplicativos web na CLI do .NET Core com o -au Windows
opção. No Visual Studio, o alterar autenticação caixa de diálogo fornece o autenticação do Windows opções.
Se a autenticação do Windows é selecionada, o aplicativo está configurado para usar o módulo do IIS da
autenticação Windows. Autenticação do Windows destina-se a sites da Intranet.
Recursos adicionais
Os artigos a seguir mostram como usar o código gerado em modelos do ASP.NET Core que usam contas de
usuário individual:
Autenticação de dois fatores com SMS
Confirmação de conta e de recuperação de senha no ASP.NET Core
Criar um aplicativo ASP.NET Core com os dados de usuário protegidos por autorização
Introdução à autorização no núcleo do ASP.NET
22/06/2018 • 2 minutes 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 ASP.NET Core com os dados de
usuário protegidos por autorização
08/01/2019 • 28 minutes 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
No ASP.NET Core 2.1, User.IsInRole Falha ao usar AddDefaultIdentity . Este tutorial usa AddDefaultIdentity e,
portanto, exige o ASP.NET Core 2.2 ou posterior. Ver esse problema de GitHub para uma solução alternativa.
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc(config =>
{
// using Microsoft.AspNetCore.Mvc.Authorization;
// using Microsoft.AspNetCore.Authorization;
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Você pode recusar a autenticação em nível de método de ação, controlador ou página Razor com o
[AllowAnonymous] atributo. Configurar a política de autenticação padrão para exigir que os usuários sejam
autenticados protege recém-adicionado páginas do Razor e controladores. Autenticação necessária por padrão é
mais segura do que contar com novos controladores e páginas do Razor para incluir o [Authorize] atributo.
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.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ContactManager.Pages
{
[AllowAnonymous]
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}
Se uma senha forte não for especificada, uma exceção é gerada quando SeedData.Initialize é chamado.
Atualização Main para usar a senha de teste:
host.Run();
}
// allowed user can create and edit contacts that they create
var managerID = await EnsureUser(serviceProvider, testUserPw, "[email protected]");
await EnsureRole(serviceProvider, managerID, Constants.ContactManagersRole);
SeedDB(context, adminID);
}
}
return user.Id;
}
if (roleManager == null)
{
throw new Exception("roleManager null");
}
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:
context.Contact.AddRange(
new Contact
{
Name = "Debra Garcia",
Address = "1234 Main St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "[email protected]",
Status = ContactStatus.Approved,
OwnerID = adminID
},
namespace ContactManager.Authorization
{
public class ContactIsOwnerAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
UserManager<IdentityUser> _userManager;
public ContactIsOwnerAuthorizationHandler(UserManager<IdentityUser>
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc(config =>
{
// using Microsoft.AspNetCore.Mvc.Authorization;
// using Microsoft.AspNetCore.Authorization;
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// 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 adicione 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 dá suporte:
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<IdentityUser> UserManager { get; }
public DI_BasePageModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> 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<IdentityUser> 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 é proprietária do 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 imperativa. As verificações
devem ser executadas depois que o aplicativo tem acesso ao recurso, carregá-los no modelo de página ou
carregá-los dentro do manipulador em si. Com frequência, você acessa 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 em exibições
Atualmente, mostra a interface do usuário a edita e excluir links para os contatos, que o usuário não é possível
modificar.
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>
}
WARNING
Ocultar links de usuários que não tem permissão para alterar os dados não proteger o aplicativo. Ocultar links torna o
aplicativo mais fácil de usar, exibindo links só é válidas. Os usuários podem hack gerados URLs para invocar editar e excluir
operações em dados que não possuem. A página do Razor ou 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.*@
<dt>
@Html.DisplayNameFor(model => model.Contact.Email)
</dt>
<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();
}
if (!isAuthorized
&& currentUserId != Contact.OwnerID
&& Contact.Status != ContactStatus.Approved)
{
return new ChallengeResult();
}
return Page();
}
if (contact == null)
{
return NotFound();
}
return RedirectToPage("./Index");
}
}
Crie um contato no navegador do administrador. Copie a URL para excluir e editar a partir do contato do
administrador. Cole esses links no navegador do usuário de teste para verificar se que o usuário de teste não é
possível executar essas operações.
Adicione Models/Contact.cs:
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
SeedData.Initialize(services, "not used");
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
Se o aplicativo propagado o banco de dados de teste. Se não houver nenhuma linha no banco de dados de
contato, o método de propagação não será executado.
Recursos adicionais
Criar um aplicativo web .NET Core e o banco de dados SQL no serviço de aplicativo do Azure
Laboratório de autorização do ASP.NET Core. Este laboratório apresenta mais detalhes sobre os recursos de
segurança introduzidos neste tutorial.
Introdução à autorização no núcleo do ASP.NET
Autorização baseada em política personalizada
Convenções de autorização de páginas do Razor no
ASP.NET Core
30/10/2018 • 6 minutes 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");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
O caminho especificado é o caminho de mecanismo de exibição, que é o caminho relativo de raiz de páginas do
Razor sem uma extensão e que contém apenas barras "/".
Uma AuthorizePage sobrecarga estará disponível se você precisa especificar uma política de autorização.
NOTE
Uma AuthorizeFilter pode ser aplicado a uma classe de modelo de página com o [Authorize] atributo de filtro. Para
obter mais informações, consulte atributo de filtro Authorize.
O caminho especificado é o caminho de mecanismo de exibição, que é o caminho relativo de raiz de páginas do
Razor.
Uma AuthorizeFolder sobrecarga estará disponível se você precisa especificar uma política de autorização.
options.Conventions.AuthorizeAreaPage("Identity", "/Manage/Accounts");
O nome da página é o caminho do arquivo sem uma extensão relativo ao diretório raiz páginas para a área
especificada. Por exemplo, o nome da página para o arquivo Areas/Identity/Pages/Manage/Accounts.cshtml é
contas/gerenciar/.
Uma AuthorizeAreaPage sobrecarga estará disponível se você precisa especificar uma política de autorização.
options.Conventions.AuthorizeAreaFolder("Identity", "/Manage");
O caminho da pasta é o caminho da pasta, relativo ao diretório raiz páginas para a área especificada. Por exemplo,
o caminho da pasta para os arquivos sob áreas/identidade/páginas/gerenciar/ é /gerenciar.
Uma AuthorizeAreaFolder 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");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
O caminho especificado é o caminho de mecanismo de exibição, que é o caminho relativo de raiz de páginas do
Razor sem uma extensão e que contém apenas barras "/".
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
O caminho especificado é o caminho de mecanismo de exibição, que é o caminho relativo de raiz de páginas do
Razor.
// This works.
.AuthorizeFolder("/Private").AllowAnonymousToPage("/Private/Public")
O inverso, no entanto, não é verdadeiro. 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.
Recursos adicionais
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
27/06/2018 • 2 minutes 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.
WARNING
[AllowAnonymous] Ignora todas as declarações de autorização. Se você combinar [AllowAnonymous] e [Authorize]
atributo, o [Authorize] atributos são ignorados. Por exemplo, se você aplicar [AllowAnonymous] no nível do
controlador, qualquer [Authorize] atributos no mesmo controlador (ou em qualquer ação dentro dele) é ignorada.
Autorização baseada em função no ASP.NET Core
31/07/2018 • 4 minutes to read • Edit Online
Quando uma identidade é criada ele pode pertencer a uma ou mais funções. Por exemplo, Tracy pode pertencer às
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 repositório de backup do processo de autorização. As funções são
expostas para o desenvolvedor por meio de IsInRole método na ClaimsPrincipal classe.
IMPORTANT
Este tópico não se aplica a Páginas Razor. Dá suporte a páginas do Razor IPageFilter e IAsyncPageFilter. Para obter mais
informações, confira Métodos de filtro para Páginas Razor.
[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 apenas ser acessado 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 requer 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 ainda mais aplicando atributos de autorização de função adicionais em nível de ação:
[Authorize(Roles = "Administrator, PowerUser")]
public class ControlPanelController : Controller
{
public ActionResult SetTime()
{
}
[Authorize(Roles = "Administrator")]
public ActionResult ShutDown()
{
}
}
Nos 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 dos 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 a 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 você deseja especificar várias funções permitidas em um requisito, você pode especificá-los como parâmetros
para o RequireRole método:
options.AddPolicy("ElevatedRights", policy =>
policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));
Este exemplo autoriza usuários que pertencem à Administrator , PowerUser ou BackupAdministrator funções.
Autorização baseada em declarações no núcleo do
ASP.NET
22/06/2018 • 6 minutes 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
11/01/2019 • 11 minutes to read • Edit Online
Nos bastidores, autorização baseada em funções e autorização baseada em declarações usam um requisito, um
manipulador de requisito e uma política previamente configurada. Esses blocos de construção dão 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—da 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 parametrizada poderia ser implementado da seguinte maneira:
using Microsoft.AspNetCore.Authorization;
NOTE
Um requisito não precisa ter dados ou propriedades.
Manipuladores de autorização
Um manipulador de autorização é responsável para a avaliação das 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.
Um requisito pode ter 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 para um requisito
Este é um exemplo de uma relação um para um em que 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 conhecido e confiável. A autorização não pode ocorrer quando a declaração está
ausente, caso em que uma tarefa concluída será retornada. Quando uma declaração estiver presente, a idade do
usuário é calculada. Se o usuário atender a idade mínima definida pelo requisito, a autorização seja considerada
bem-sucedida. Quando a autorização for bem-sucedida, 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 pode lidar com três
tipos diferentes de 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 percorre PendingRequirements—uma propriedade que contém requisitos não marcada como
bem-sucedido. Para um ReadPermission requisito, o usuário deve ser um proprietário ou um responsável para
acessar o recurso solicitado. No caso de um EditPermission ou DeletePermission requisito, ele ou ela deve ser
um proprietário para acessar o recurso solicitado.
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) causam curto-circuito a execução de manipuladores quando context.Fail é chamado.
InvokeHandlersAfterFailure o padrão é true , caso em que todos os manipuladores são chamados. Isso permite
que os requisitos produzir efeitos colaterais, como 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 da política é bem-sucedida.
// Get or set the Age property by manipulating the underlying Policy property
public int Age
{
get
{
if (int.TryParse(Policy.Substring(POLICY_PREFIX.Length), out var age))
{
return age;
}
return default(int);
}
set
{
Policy = $"{POLICY_PREFIX}{value.ToString()}";
}
}
}
Esse tipo de atributo tem um Policy cadeia de caracteres com base no prefixo embutido em código (
"MinimumAge" ) e um número inteiro passado por meio do construtor.
Você pode aplicá-lo às ações da mesma maneira que outras Authorize atributos, exceto que assume um inteiro
como um parâmetro.
[MinimumAgeAuthorize(10)]
public IActionResult RequiresMinimumAge10()
IAuthorizationPolicyProvider personalizado
Personalizado MinimumAgeAuthorizeAttribute facilita a políticas de autorização da solicitação para qualquer idade
mínima desejada. O próximo problema a resolver é verificar se as políticas de autorização estão disponíveis para
todas as idades diferentes. É aí que um IAuthorizationPolicyProvider é útil.
Ao usar MinimumAgeAuthorizationAttribute , os nomes de política de autorização seguirá o padrão
"MinimumAge" + Age , então personalizado IAuthorizationPolicyProvider deve gerar diretivas de autorização por:
return Task.FromResult<AuthorizationPolicy>(null);
}
}
Política padrão
Além de fornecer políticas de autorização nomeado, um personalizado IAuthorizationPolicyProvider precisa
implementar GetDefaultPolicyAsync para fornecer uma política de autorização para [Authorize] atributos sem um
nome de política especificado.
Em muitos casos, esse atributo de autorização requer apenas um usuário autenticado, portanto, você pode fazer a
diretiva necessária com uma chamada para RequireAuthenticatedUser :
Assim como acontece com todos os aspectos de um personalizado IAuthorizationPolicyProvider , você pode
personalizá-lo, conforme necessário. Em alguns casos:
Políticas de autorização padrão não podem ser usadas.
Recuperando a política padrão pode ser designado como um fallback IAuthorizationPolicyProvider .
Use um IAuthorizationPolicyProvider personalizado
Para usar políticas personalizadas de um IAuthorizationPolicyProvider , você deve:
Registrar apropriado AuthorizationHandler tipos de injeção de dependência (descrito na autorização baseada
em política), assim como acontece com todos os cenários de autorização baseada em política.
Registrar personalizado IAuthorizationPolicyProvider tipo na coleção de serviço de injeção de dependência do
aplicativo (em Startup.ConfigureServices ) para substituir o provedor de política padrão.
services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
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ê queira 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 inseri-los em seu
construtor.
Por exemplo, se você quiser usar o ASP. NET do log a infraestrutura que você deseja injetar ILoggerFactory em
seu manipulador. Um manipulador desse tipo pode parecer com:
services.AddSingleton<IAuthorizationHandler, LoggingAuthorizationHandler>();
Uma instância do manipulador será criado quando o aplicativo for iniciado e será de DI injetar registrado
ILoggerFactory em seu construtor.
NOTE
Manipuladores que usam o Entity Framework não devem ser registrados como singletons.
Autorização baseada em recursos no ASP.NET Core
16/11/2018 • 8 minutes to read • Edit Online
Estratégia de autorização depende do recurso que está sendo acessado. Considere 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 que a avaliação de autorização pode ocorrer.
Avaliação de atributo ocorre antes da vinculação de dados e antes da execução do manipulador de página ou ação
que carrega o documento. Por esses motivos, a autorização declarativa com um [Authorize] atributo não é
suficiente. Em vez disso, você pode invocar um método de autorização personalizada—um estilo conhecido como
autorização imperativa.
Exibir ou baixar um código de exemplo (como baixar).
Criar um aplicativo ASP.NET Core com os dados de usuário protegidos por autorização contém um aplicativo de
exemplo que usa a autorização baseada em recursos.
IAuthorizationService tem dois AuthorizeAsync sobrecargas de método: uma aceitando o recurso e o nome da
política e a outra aceitando o recurso e uma lista de requisitos para avaliar.
NOTE
O código a seguir exemplos pressupõem que a autenticação foi executada 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();
}
}
[HttpGet]
public async Task<IActionResult> Edit(Guid documentId)
{
Document document = _documentRepository.Find(documentId);
if (document == null)
{
return new NotFoundResult();
}
if (await _authorizationService
.AuthorizeAsync(User, document, "EditPolicy"))
{
return View(document);
}
else
{
return new ChallengeResult();
}
}
return Task.CompletedTask;
}
}
No exemplo anterior, imagine que SameAuthorRequirement é um caso especial de uma mais genérica
SpecificAuthorRequirement classe. O SpecificAuthorRequirement classe (não mostrado) contém um Name
propriedade que representa o nome do autor. O Name propriedade pode ser definida como o usuário atual.
Registrar o requisito e o manipulador no Startup.ConfigureServices :
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 operações CRUD (Create, Read, Update, Delete), use
o OperationAuthorizationRequirement classe auxiliar. Essa classe permite que você escreva 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 recursos operacionais, especifique a operação ao invocar AuthorizeAsync em seu
manipulador de página ou ação. O exemplo a seguir determina se o usuário autenticado tem permissão para exibir
o documento.
NOTE
O código a seguir exemplos pressupõem que a autenticação foi executada 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 for bem-sucedida, a página para exibir o documento é retornada. Se falhar de autorização, mas o
usuário é autenticado, o retorno de ForbidResult informa qualquer middleware de autenticação que houve falha
na autorização. Um ChallengeResult é retornado quando a autenticação deve ser executada. Para clientes de
navegador interativo, ele pode ser apropriado redirecionar o usuário para uma página de logon.
[HttpGet]
public async Task<IActionResult> View(Guid documentId)
{
Document document = _documentRepository.Find(documentId);
if (document == null)
{
return new NotFoundResult();
}
if (await _authorizationService
.AuthorizeAsync(User, document, Operations.Read))
{
return View(document);
}
else
{
return new ChallengeResult();
}
}
Se a autorização for bem-sucedida, o modo de exibição para o documento é retornado. Se a autorização falhar,
retornando ChallengeResult informa qualquer middleware de autenticação que houve falha na autorização e o
middleware pode levar a resposta apropriada. Uma resposta apropriada pode estar retornando um código de
status 401 ou 403. Para clientes de navegador interativo, isso poderia significar a redirecionar o usuário para uma
página de logon.
Autorização baseada em modo de exibição no
ASP.NET Core MVC
30/07/2018 • 2 minutes to read • Edit Online
Um desenvolvedor geralmente deseja 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
injeção de dependência. Para injetar o serviço de autorização em um modo de exibição do Razor, use o @inject
diretiva:
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
Se você quiser que o serviço de autorização em cada exibição, coloque o @inject diretiva na viewimports. cshtml
arquivo do exibições directory. 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ê
poderia verificar durante autorização baseada em recursos:
ASP.NET Core 2.x
ASP.NET Core 1.x
Em alguns casos, o recurso será o seu modelo de exibição. Invocar AuthorizeAsync exatamente da mesma forma
que você poderia 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 da política deve levar em consideração.
WARNING
Não confie na alternância de visibilidade de elementos de interface do usuário do seu aplicativo como a verificação de
autorização única. 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 é de
URL do método de ação, se ele ou ela sabe que o recurso relativo /Document/Edit/1. Por esse motivo, o Edit método de
ação deve realizar sua própria verificação de autorização.
Autorizar com um esquema específico no ASP.NET
Core
26/10/2018 • 6 minutes 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 a autenticação baseada em cookies para fazer logon e autenticação do portador
do 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 em que um contém uma identidade
básica e o outro é criado quando a autenticação multifator (MFA) foi acionada. MFA pode ser acionado porque o
usuário solicitou uma operação que exige segurança extra.
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 o
portador.
NOTE
Especificar o esquema padrão resulta no HttpContext.User propriedade sendo definida como essa identidade. Se esse
comportamento não for desejado, desabilitá-lo, invocando o formulário sem parâmetros de AddAuthentication .
[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)]
public class MixedController : Controller
No código anterior, somente o manipulador com o esquema de "Bearer" é executado. Todas as identidades
baseadas em cookies serã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 "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
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "https://localhost:5000/";
options.Authority = "https://localhost:5000/identity/";
})
.AddJwtBearer("AzureAD", options =>
{
options.Audience = "https://localhost:5000/";
options.Authority = "https://login.microsoftonline.com/eb971100-6f99-4bdc-8611-1bc8edd7f436/";
});
}
NOTE
Apenas uma autenticação de portador JWT está registrada com o esquema de autenticação padrão
JwtBearerDefaults.AuthenticationScheme . Autenticação adicional deve ser registrado com um esquema de autenticação
exclusiva.
A próxima etapa é atualizar a política de autorização padrão para aceitar os dois esquemas de autenticação. Por
exemplo:
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme,
"AzureAD");
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
}
Como a política de autorização padrão é substituída, é possível usar um simples [Authorize] atributo em
controladores. O controlador, em seguida, aceita solicitações com JWT emitido pelo emissor do primeiro ou
segundo.
Proteção de dados do ASP.NET Core
26/10/2018 • 11 minutes to read • Edit Online
Aplicativos da Web geralmente precisam armazenar dados confidenciais de segurança. Windows fornece
DPAPI para aplicativos da área de trabalho, 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 rotação e gerenciamento de chaves.
A pilha de proteção de dados do ASP.NET Core foi projetada para servir como a substituição de longo prazo
para o <machineKey> elemento no ASP.NET 1. x - 4. x. Ele foi projetado para resolver muitos dos problemas
da pilha de criptografia antigo, fornecendo 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 uma única sentença sucintamente: eu preciso manter
informações confiáveis para recuperação posterior, mas eu não confio o mecanismo de persistência. Em
termos de web, isso pode ser escrito como "Eu preciso de ida e volta 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" de token e entrega-o para o cliente. Em uma data futura, o cliente apresentará esse
token de volta 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).
Uma vez que 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 dos dados específicos do
servidor. Essas informações, em geral, não deveriam ser divulgadas para um cliente não confiável. Portanto, o
segundo requisito: confidencialidade.
Por fim, como aplicativos modernos são divididos em componentes, o que vimos é componentes individuais
vai querer tirar proveito desse sistema sem levar em consideração outros componentes no sistema. Por
exemplo, se um componente de token de portador é usando essa pilha, ele deve operar sem interferência de
um mecanismo de anti-CSRF que também pode usar a mesma pilha. Portanto, o requisito final: isolamento.
Podemos fornecer ainda mais as restrições para limitar o escopo de nossos requisitos. Vamos supor que todos
os serviços que operam com o sistema de criptografia são igualmente confiáveis e que os dados não precisam
ser gerado ou consumido fora os serviços sob nosso controle direto. Além disso, exigimos que as operações
são tão rápidas quanto possível, pois cada solicitação ao serviço web pode percorrer o sistema criptográfico
uma ou mais vezes. Isso torna a criptografia simétrica ideal para o nosso cenário, e podemos pode desconto
criptografia assimétrica até que tal uma vez que ele é necessário.
Filosofia de design
Começamos por meio da identificação de problemas com a pilha existente. Depois que tivermos, nós
pesquisamos o panorama das soluções existentes e concluiu que não há solução existente teve muito os
recursos que são procurados. Em seguida, projetamos uma solução baseada em vários princípios de
orientação.
O sistema deve oferecer a simplicidade da configuração. O ideal é que o sistema seria sem nenhuma
configuração e os desenvolvedores poderiam parta para a açã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.
Oferecem uma API voltado ao consumidor simples. As APIs devem ser fáceis de usar corretamente e
difícil de usar incorretamente.
Os desenvolvedores não devem aprender os princípios de gerenciamento de chaves. O sistema deve
lidar com a seleção de algoritmo e a vida útil da chave em nome do desenvolvedor. O ideal é que o
desenvolvedor nunca mesmo deve ter acesso ao material da 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á-la automaticamente.
Com esses princípios em mente, desenvolvemos um simples fácil de usar pilha da proteção de dados.
As APIs de proteção de dados do ASP.NET Core não destinam principalmente para persistência indefinida de
cargas confidenciais. Outras tecnologias, como DPAPI do Windows CNG e do 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 que usa as APIs de
proteção de dados do 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
principal públicos-alvo; de destino
1. O visão geral das APIs de consumidor os desenvolvedores de aplicativo e estrutura de destino.
"Quero saber mais sobre como funciona a pilha de ou sobre como ela é configurada. Simplesmente
quero executar alguma operação no tão simples uma maneira possível com a alta probabilidade de usar
as APIs com êxito".
2. O APIs de configuração os desenvolvedores de aplicativos e administradores de sistema de destino.
"Eu preciso informar ao sistema de proteção de dados de que meu ambiente requer configurações ou
os caminhos não padrão."
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 de
reconhecimento de segurança.
"Eu preciso substituir um componente inteiro dentro do sistema porque tenho requisitos
comportamentais realmente exclusivos. Desejo saber usados raramente partes da superfície de API a
fim de criar um plug-in que atenda aos meus requisitos."
Layout do pacote
A pilha da proteção de dados consiste em cinco pacotes.
Microsoft.AspNetCore.DataProtection.Abstractions contém o IDataProtectionProvider e IDataProtector
interfaces para criar serviços de proteção de dados. Ele também contém métodos de extensão úteis
para trabalhar com esses tipos (por exemplo, IDataProtector.Protect). Se o sistema de proteção de
dados é instanciado em outro lugar e você está consumindo a API, referência
Microsoft.AspNetCore.DataProtection.Abstractions .
Recursos adicionais
Hospedar o ASP.NET Core em um web farm
Comece com as APIs de proteção de dados no
ASP.NET Core
22/08/2018 • 3 minutes to read • Edit Online
/*
* SAMPLE OUTPUT
*
* Enter input: Hello world!
* Protect returned: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
* Unprotect returned: Hello world!
*/
Quando você cria um protetor 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 finalidade de "verde" seria capaz de Desproteger dados fornecidos por um protetor, com a
finalidade de "roxo".
TIP
Instâncias do IDataProtectionProvider e IDataProtector sejam thread-safe para chamadores vários. Ele deve 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 o conteúdo protegido não pode ser verificado ou
decifrado. Alguns componentes talvez queira ignorar erros durante a Desproteger operações; um componente que lê os
cookies de autenticação pode manipular esse erro e tratar a solicitação como se ele não tinha nenhum cookie em todos os
em vez de falhar a solicitação imediatamente. Componentes que desejam esse comportamento devem especificamente
capturar CryptographicException em vez de assimilação de todas as exceções.
Visão geral APIs do consumidor do ASP.NET Core
22/06/2018 • 7 minutes 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!
*/
TIP
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
22/06/2018 • 6 minutes 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.
TIP
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.
WARNING
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.
NOTE
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 finalidade e a multilocação no ASP.NET
Core
23/08/2018 • 3 minutes to read • Edit Online
Uma vez que um IDataProtector também é implicitamente um IDataProtectionProvider , fins podem ser
encadeada juntos. Nesse sentido provider.CreateProtector([ "purpose1", "purpose2" ]) é equivalente a
provider.CreateProtector("purpose1").CreateProtector("purpose2") .
Isso permite que algumas relações hierárquicas interessantes através do sistema de proteção de dados. No
exemplo anterior de Contoso.Messaging.SecureMessage, o componente SecureMessage pode chamar
provider.CreateProtector("Contoso.Messaging.SecureMessage") iniciais de uma vez e armazenar em cache o
resultado em uma particular _myProvider campo. Protetores de futuros, em seguida, podem ser criadas por meio
de chamadas para _myProvider.CreateProtector("User: username") , e os protetores seriam usados para proteger as
mensagens individuais.
Isso também pode ser invertido. Considere um único aplicativo lógico 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. O aplicativo guarda-chuva tem um único provedor mestre e, em seguida, 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, pode derivar seus próprios
protetores individuais com base em suas necessidades, mas, independentemente de como eles tentam eles não é
possível criar protetores que entrem em conflito com qualquer outro locatário no sistema. Graficamente, isso é
representado como abaixo.
WARNING
Isso pressupõe que a cobertura do controles de aplicativo, quais 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 poderiam
executar a reflexão privada para quebrar as garantias de isolamento, ou pode apenas ler o material de chave mestra
diretamente e derivar qualquer subchaves que desejarem.
Na verdade, o sistema de proteção de dados usa uma espécie de multilocação em sua configuração de out-of-the-
box do padrão. Por padrão, o material de chave mestra é armazenado na pasta de perfil do usuário da conta de
processo do operador (ou o registro, para identidades do pool de aplicativos IIS ). Mas é, na verdade, bastante
comum usar uma única conta para executar vários aplicativos e, portanto, todos esses aplicativos seriam acabam
compartilhando a material da chave mestra. Para resolver isso, o sistema de proteção de dados insere
automaticamente um identificador exclusivo por aplicativo como o primeiro elemento na cadeia de finalidade geral.
Que serve para essa finalidade implícita isolar aplicativos individuais uns dos outros, tratando efetivamente cada
aplicativo como um locatário exclusivo no sistema e o processo de criação de protetor parece idêntico na imagem
acima.
Hash de senhas no ASP.NET Core
18/09/2018 • 2 minutes to read • Edit Online
/*
* SAMPLE OUTPUT
*
* Enter a password: Xtw9NMgx
* Salt: NZsP6NnmfBuYeJrrAKNuVQ==
* Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
*/
Consulte a 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
22/06/2018 • 5 minutes 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.
WARNING
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 revogadas no
ASP.NET Core
26/10/2018 • 5 minutes to read • Edit Online
As APIs de proteção de dados do ASP.NET Core não destinam principalmente para persistência indefinida de
cargas confidenciais. Outras tecnologias, como DPAPI do Windows CNG e do 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 que usa as APIs de proteção de dados
do ASP.NET Core para proteção de longo prazo de dados confidenciais. As chaves nunca são removidas do anel de
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 os dados que foi protegidos com uma
chave revogada, como IDataProtector.Unprotect lançará uma exceção, nesse caso. Isso pode ser adequado 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 fazer logon
novamente. Mas para cargas persistentes, tendo Unprotect throw pode levar à perda de dados inaceitável.
IPersistedDataProtector
Para suportar o cenário de permitir que os conteúdos a serem desprotegidos mesmo diante de chaves revogadas,
o sistema de proteção de dados contém um IPersistedDataProtector tipo. Para obter uma instância de
IPersistedDataProtector , simplesmente obtém uma instância do IDataProtector no modo normal e tente
conversão a IDataProtector para IPersistedDataProtector .
NOTE
Nem todos os IDataProtector instâncias podem ser convertidas em IPersistedDataProtector . Os desenvolvedores
devem usar o C# operador ou semelhante ou evitar exceções de tempo de execução causado por conversões inválidas, e eles
devem estar preparados para tratar o caso de falha de forma adequada.
Essa API usa o conteúdo protegido (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á ser definido como true se a chave usada para proteger esse conteúdo não é mais a
chave padrão do Active Directory, por exemplo, a chave usada para proteger essa carga é antiga e uma
chave sem interrupção operação tem desde ocorrido. O chamador poderá considerar o proteger novamente
a carga, dependendo de suas necessidades de negócios.
wasRevoked : será definido como true se a chave usada para proteger este conteúdo foi revogada.
WARNING
Tenha bastante cuidado ao passar ignoreRevocationErrors: true para o DangerousUnprotect método. Se, depois de
chamar esse método o wasRevoked valor for true, em seguida, a chave usada para proteger este conteúdo foi revogada e
autenticidade do conteúdo deve ser tratada como suspeito. Nesse caso, apenas continue a operar na carga de desprotegidos
se você tiver alguma garantia separada que é autêntico, por exemplo, se ele é proveniente de um banco de dados seguro em
vez de que está 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
21/06/2018 • 2 minutes 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
22/01/2019 • 19 minutes to read • Edit Online
Quando o sistema de proteção de dados é inicializado, ele se aplica as configurações padrão com base no
ambiente operacional. Essas configurações são geralmente apropriadas para aplicativos em execução em
um único computador. Há casos em que um desenvolvedor talvez queira alterar as configurações padrão:
O aplicativo é distribuído entre várias máquinas.
Por motivos de conformidade.
Para esses cenários, o sistema de proteção de dados oferece uma API de configuração avançada.
WARNING
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 as chaves em repouso, mas isso não impede que os
invasores desde a criação de novas chaves. Consequentemente, a segurança desse aplicativo é afetada. O local de
armazenamento configurado com proteção de dados deve ter seu acesso limitado ao próprio aplicativo, semelhante
à forma como você protegerá os arquivos de configuração. Por exemplo, se você optar por armazenar seu token de
autenticação no disco, use as permissões do sistema de arquivos. Certifique-se apenas a identidade sob a qual seu
aplicativo web é executado tem ler, gravar e criar acesso a esse diretório. Se você usar o armazenamento de tabelas
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 configurar a proteção de dados de opções.
ProtectKeysWithAzureKeyVault
Para armazenar as chaves na Azure Key Vault, configure o sistema com ProtectKeysWithAzureKeyVault no
Startup classe:
PersistKeysToFileSystem
Para armazenar chaves em um compartilhamento UNC, em vez da a % LOCALAPPDATA % local padrão,
configure o sistema com PersistKeysToFileSystem:
WARNING
Se você alterar o local de persistência de chave, o sistema não criptografa automaticamente os chaves em repouso,
já que ele não sabe se o DPAPI é um mecanismo de criptografia apropriado.
ProtectKeysWith*
Você pode configurar o sistema para proteger as chaves em repouso chamando 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:
Ver chave de criptografia em repouso para obter mais exemplos e uma discussão sobre os mecanismos de
criptografia de chave interna.
UnprotectKeysWithAnyCertificate
No ASP.NET Core 2.1 ou posterior, você pode girar certificados e descriptografar chaves em repouso
usando uma matriz de X509Certificate2 certificados com UnprotectKeysWithAnyCertificate:
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 os aplicativos uns dos outros com base em seus
caminhos de conteúdo raiz, mesmo que compartilham o mesmo repositório de chave físico. Isso impede
que os aplicativos Noções básicas sobre cargas protegidas uns dos outros.
Compartilhar protegido cargas entre aplicativos:
Configurar SetApplicationName em cada aplicativo com o mesmo valor.
Use a mesma versão da pilha de API de proteção de dados entre os aplicativos. Realizar qualquer das
seguintes opções em arquivos de projeto de aplicativos:
A mesma versão da estrutura compartilhada por meio de referência a metapacote Microsoft.
Referência à mesma pacote de proteção de dados versão.
DisableAutomaticKeyGeneration
Você pode ter um cenário onde você não deseja que um aplicativo para reverter automaticamente chaves
(criar novas chaves), como eles abordam a expiração. Um exemplo disso pode ser configurados em uma
relação primária/secundária, em que apenas o aplicativo principal é responsável por questões de
gerenciamento de chaves e aplicativos secundários simplesmente tem uma exibição somente leitura do
anel de chave de aplicativos. Os aplicativos secundários podem ser configurados para tratar o anel de
chave como somente leitura ao configurar o sistema com DisableAutomaticKeyGeneration:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.DisableAutomaticKeyGeneration();
}
services.AddDataProtection()
.UseCryptographicAlgorithms(
new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
services.AddDataProtection()
.UseCryptographicAlgorithms(
new AuthenticatedEncryptionSettings()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
TIP
Algoritmos a alteração não afeta as chaves existentes do anel de chave. Ela afeta apenas as chaves recentemente
geradas.
serviceCollection.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new ManagedAuthenticatedEncryptorConfiguration()
{
// A type that subclasses SymmetricAlgorithm
EncryptionAlgorithmType = typeof(Aes),
// Specified in bits
EncryptionAlgorithmKeySize = 256,
// Specified in bits
EncryptionAlgorithmKeySize = 256,
Geralmente, o *propriedades de tipo devem apontar para concreto, implementações que pode ser
instanciadas (por meio de um construtor sem parâmetros público) de SymmetricAlgorithm e
KeyedHashAlgorithm, embora o sistema classifica como caso especial, como alguns valores typeof(Aes)
para sua conveniência.
NOTE
O SymmetricAlgorithm deve ter um comprimento de chave de 128 bits ≥ e um tamanho de bloco de 64 bits do ≥, e
ele deve oferecer suporte à criptografia de modo CBC com preenchimento 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 do resumo do algoritmo de hash. O KeyedHashAlgorithm não é estritamente necessária para ser
HMAC.
services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngCbcAuthenticatedEncryptorConfiguration()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256,
// Passed to BCryptOpenAlgorithmProvider
HashAlgorithm = "SHA256",
HashAlgorithmProvider = null
});
Para especificar um algoritmo personalizado do CNG do Windows usando a criptografia de modo CBC
com validação do HMAC, crie uma CngCbcAuthenticatedEncryptionSettings instância que contém as
informações de algoritmos:
services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngCbcAuthenticatedEncryptionSettings()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256,
// Passed to BCryptOpenAlgorithmProvider
HashAlgorithm = "SHA256",
HashAlgorithmProvider = null
});
NOTE
O algoritmo de criptografia de bloco simétricas 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 PKCS #7. O
algoritmo de hash deve ter um tamanho de resumo de > = 128 bits e deve oferecer suporte a que está sendo
aberto com o BCRYPT_ALG_MANIPULAR_HMAC_sinalizador de sinalizador. O *propriedades do provedor podem ser
definidas como nulas para usar o provedor padrão para o algoritmo especificado. Consulte a
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
});
services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngGcmAuthenticatedEncryptionSettings()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256
});
NOTE
O algoritmo de criptografia de bloco simétricas deve ter um comprimento de chave de > = 128 bits, um tamanho
de bloco de exatamente de 128 bits, e ele deve oferecer suporte a criptografia do GCM. Você pode definir as
EncryptionAlgorithmProvider propriedade como nulo para usar o provedor padrão para o algoritmo especificado.
Consulte a BCryptOpenAlgorithmProvider documentação para obter mais informações.
Recursos adicionais
Cenários de reconhecimento não DI para proteção de dados no ASP.NET Core
Suporte a política de máquina de proteção de dados no ASP.NET Core
Hospedar o ASP.NET Core em um web farm
Gerenciamento de chaves de proteção de dados e o
tempo de vida no ASP.NET Core
18/07/2018 • 5 minutes to read • Edit Online
Gerenciamento de chaves
O aplicativo tenta detectar seu ambiente operacional e lidar com a configuração de chave por conta própria.
1. Se o aplicativo estiver hospedado em aplicativos do Azure, as chaves são persistidas para o
%HOME%\ASP.NET\DataProtection-Keys pasta. Essa pasta é apoiada pelo repositório de rede e é
sincronizada em todos os computadores que hospedam o aplicativo.
As chaves não são protegidas em repouso.
O DataProtection chaves pasta fornece o anel de chave a todas as instâncias de um aplicativo em um
único slot de implantação.
Slots de implantação separados, como de preparo e produção, não compartilham um anel de chave.
Quando você alternar entre os slots de implantação, por exemplo, trocar o preparo e produção ou
usando um teste a / B, 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 desejar que os
anéis de chave independente de slot, use um provedor de anel de chave externo, como o
armazenamento de BLOBs Azure, Azure Key Vault, um repositório SQL ou do cache Redis.
2. Se o perfil do usuário estiver disponível, as chaves são persistidas 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 estiver hospedado no IIS, as chaves são mantidas no registro HKLM em uma chave de
registro especial que tem a ACL acessível apenas para a conta de processo de trabalho. As chaves são
criptografadas em repouso usando a DPAPI.
4. Se nenhuma dessas condições corresponderem, as chaves não são persistidas fora do processo atual.
Quando o processo é encerrado, todas geradas chaves forem 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 bons padrões para a maioria dos aplicativos de forma semelhante a
como o ASP.NET <machineKey > rotinas de geração automática funcionavam no passado. A opção de fallback
final é o único cenário que exige que o desenvolvedor especifique configuração antecipado se quiserem
persistência de chave, mas essa fallback só ocorre em situações raras.
Ao hospedar em um contêiner do Docker, as chaves devem ser persistentes em uma pasta que é um volume do
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 web farm se os aplicativos não podem acessar um volume compartilhado de rede (consulte
PersistKeysToFileSystem para obter mais informações).
WARNING
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 em repouso pode ser
reabilitada por meio configuração.
Algoritmos padrão
O algoritmo de proteção de conteúdo 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 de
subpropriedades usadas para esses algoritmos em uma base por carga. Ver subchave derivação para obter mais
informações.
Recursos adicionais
Extensibilidade de gerenciamento de chaves no ASP.NET Core
Hospedar o ASP.NET Core em um web farm
Suporte a política de máquina de proteção de
dados no ASP.NET Core
22/06/2018 • 7 minutes to read • Edit Online
WARNING
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.
WARNING
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
22/06/2018 • 4 minutes 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();
}
}
TIP
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
21/06/2018 • 2 minutes to read • Edit Online
WARNING
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.
NOTE
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".
TIP
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()
NOTE
O IServiceProvider transmitido ao construtor pode ser nulo.
TIP
Leia as gerenciamento de chaves seção antes de ler esta seção, pois ele explica alguns dos conceitos fundamentais dessas
APIs.
WARNING
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 no sistema de criptografia. A chave do termo é usada
aqui no sentido abstrato, não no sentido literal "criptográfico do material da chave". Uma chave tem as seguintes
propriedades:
Datas de expiração, criação e ativação
Status de revogação
Identificador de chave (um GUID )
Além disso, IKey expõe uma CreateEncryptor método que pode ser usado para criar um
IAuthenticatedEncryptor instância vinculado a essa chave.
Além disso, IKey expõe uma CreateEncryptorInstance método que pode ser usado para criar um
IAuthenticatedEncryptor instância vinculado a essa chave.
NOTE
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
a manipulação. Ele expõe três operações de alto nível:
Criar uma nova chave e mantê-lo no armazenamento.
Obter todas as chaves de armazenamento.
Revogar uma ou mais chaves e persistir as informações de revogação para o armazenamento.
WARNING
Escrevendo um IKeyManager é uma tarefa muito avançada e a maioria dos desenvolvedores não deve tentá-lo. Em vez
disso, a maioria dos desenvolvedores deve tirar vantagem dos recursos oferecidos pelo XmlKeyManager classe.
XmlKeyManager
O XmlKeyManager tipo é a implementação concreta da caixa de entrada da IKeyManager . Ele fornece vários recursos
úteis, incluindo caução de chaves e criptografia de chaves em repouso. Chaves nesse sistema são representadas
como elementos XML (especificamente, XElement).
XmlKeyManager depende de vários outros componentes no decorrer de cumprir suas tarefas:
AlgorithmConfiguration , que determina os algoritmos usados por novas chaves.
IXmlRepository , que controla onde as chaves são mantidas no armazenamento.
IXmlEncryptor [opcional], que permite criptografar as 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 vinculados dentro
XmlKeyManager .
IXmlRepository
O IXmlRepository interface representa um tipo que pode persistir o XML para e recuperar o XML de um
armazenamento de backup. Ele expõe duas APIs:
GetAllElements : IReadOnlyCollection<XElement>
StoreElement(XElement element, string friendlyName)
Implementações de IXmlRepository não precisam analisar o XML passar através deles. Eles devem tratar os
documentos XML como opaco e permitir que camadas superiores se preocupar sobre como gerar e analisar os
documentos.
Há quatro tipos concretos internos que implementam IXmlRepository :
FileSystemXmlRepository
RegistryXmlRepository
AzureStorage.AzureBlobXmlRepository
RedisXmlRepository
FileSystemXmlRepository
RegistryXmlRepository
AzureStorage.AzureBlobXmlRepository
RedisXmlRepository
Consulte a documentos de provedores de armazenamento de chaves para obter mais informações.
Registrando um personalizado IXmlRepository é apropriado ao usar um armazenamento de backup diferente (por
exemplo, armazenamento de tabela do Azure).
Para alterar o repositório padrão todo o aplicativo, registre um personalizado IXmlRepository instância:
services.AddSingleton<IXmlRepository>(new MyCustomXmlRepository());
IXmlEncryptor
O IXmlEncryptor interface representa um tipo que pode criptografar um elemento XML de texto sem formatação.
Ela apresenta uma única API:
Encrypt(XElement plaintextElement): EncryptedXmlInfo
Se um serializado IAuthenticatedEncryptorDescriptor contém quaisquer elementos marcados como "requer
criptografia", em seguida, XmlKeyManager executará esses elementos por meio do configurado IXmlEncryptor do
Encrypt método e ele serão mantido o elemento adulterem em vez de elemento de texto sem formatação para o
IXmlRepository . A saída a Encrypt método é um EncryptedXmlInfo objeto. Esse objeto é um wrapper que contém
os dois o resultante adulterem XElement e o tipo que representa um IXmlDecryptor que pode ser usado para
decifrar o elemento correspondente.
Há quatro tipos concretos internos que implementam IXmlEncryptor :
CertificateXmlEncryptor
DpapiNGXmlEncryptor
DpapiXmlEncryptor
NullXmlEncryptor
Consulte a criptografia de chave em documentos do rest para obter mais informações.
Para alterar o mecanismo padrão de chave criptografia em repouso todo o aplicativo, registre um personalizado
IXmlEncryptor instância:
services.AddSingleton<IXmlEncryptor>(new MyCustomXmlEncryptor());
IXmlDecryptor
O IXmlDecryptor interface representa um tipo que sabe como descriptografar um XElement que foi adulterem por
meio de um IXmlEncryptor . Ela apresenta uma única API:
Descriptografe (encryptedElement XElement): XElement
O método desfaz a criptografia executada pelo IXmlEncryptor.Encrypt . Em geral, cada concreto
Decrypt
IXmlEncryptor implementação terá um concreto correspondente IXmlDecryptor implementação.
Tipos que implementam IXmlDecryptor deve ter um dos dois construtores públicos a seguir:
.ctor(IServiceProvider)
.ctor()
NOTE
O IServiceProvider passado para o construtor pode ser nulo.
IKeyEscrowSink
O IKeyEscrowSink interface representa um tipo que pode executar a caução de informações confidenciais.
Lembre-se de que descritores serializados podem conter informações confidenciais (por exemplo, o material
criptográfico), e esse é 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 hachura de escape de emergência, permitindo o acesso ao XML bruto
serializado antes que ele é transformado por qualquer configurados IXmlEncryptor. A interface expõe uma única
API:
Store (keyId Guid, o elemento de XElement)
Cabe ao 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 reconhecido onde chave privada do certificado tenha sido mantida
em garantia; 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 de caução está habilitado, embora os administradores de servidor podem
configurar isso globalmente. Ele também pode ser configurado por meio de programação por meio de
IDataProtectionBuilder.AddKeyEscrowSink método conforme mostrado no exemplo a seguir. 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
registradas, cada um deles será chamado durante a geração de chave, para que as chaves podem ser mantida 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: ele deve disponibilizar o material da chave para uma autoridade confiável, e uma vez que o
aplicativo em si não é uma autoridade confiável, ele não deve ter acesso ao seu próprio caucionada material.
O código de exemplo a seguir demonstra como criar e registrar um IKeyEscrowSink onde as chaves são mantida
em garantia, de modo que somente os membros do grupo "administradores de CONTOSODomain" poderá
recuperá-los.
NOTE
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;
using Microsoft.Extensions.Logging;
// 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;
/*
* 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
22/06/2018 • 2 minutes to read • Edit Online
WARNING
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
21/06/2018 • 2 minutes 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.
WARNING
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
22/06/2018 • 8 minutes 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.
NOTE
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:
saída: = keyModifier | | IV | | E_cbc (dados K_E, iv,) | | HMAC (K_H, iv | | E_cbc (dados K_E, iv,))
NOTE
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).
NOTE
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.
resultado: = 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:
Criada - a chave existe no anel de chave, mas ainda não foi ativada. A chave não deve ser usada para
novas operações de proteção até que haja tempo suficiente tenha decorrido a chave tem tido a
oportunidade de se propagar para todos os computadores que estão consumindo esse anel de chave.
Ativo - a chave existe no anel de chave e deve ser usado para todas as operações de proteção de novo.
Expirou - a chave executou seu tempo de vida natural e não deve mais ser usada para novas operações de
proteção.
Revogado - a chave for comprometida e não deve ser usada para novas operações de proteção.
Todos criadas, ativas e vencidas chaves podem ser usadas para desproteger cargas de entrada. Chaves
revogadas por padrão não podem ser usadas para desproteger cargas, mas o desenvolvedor de aplicativos pode
substituir esse comportamento se necessário.
WARNING
O desenvolvedor pode ser tentado a 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. Excluir uma chave
é verdadeiramente destrutivo comportamento e, consequentemente, o sistema de proteção de dados não expõe nenhuma
API de primeira classe para realizar 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.
WARNING
Os desenvolvedores devem muito raramente (se utilizadas) precisará usar as 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 injeção de dependência que forneceu a instância do
IDataProtectionProvider também pode fornecer uma instância de IKeyManager para seu consumo. Como
alternativa, você pode extrair os IKeyManager diretamente do IServiceProvider como no exemplo a seguir.
Qualquer operação que modifica o anel de chave (Criando uma nova chave explicitamente ou executar 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 releia 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 Revogando existente de 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, no qual ele tenta deduzir um mecanismo de criptografia em
repouso e o local de armazenamento de chave apropriado automaticamente. O mecanismo de persistência de
chave também é configurável pelo desenvolvedor do aplicativo. Os documentos a seguir discutem as
implementações de caixa de entrada desses mecanismos:
Provedores de armazenamento de chaves no ASP.NET Core
Chave de criptografia em repouso no ASP.NET Core
Provedores de armazenamento de chaves no
ASP.NET Core
27/12/2018 • 6 minutes to read • Edit Online
O sistema de proteção de dados emprega um mecanismo de descoberta por padrão para determinar onde as
chaves de criptografia devem ser persistente. O desenvolvedor pode substituir o mecanismo de descoberta
padrão e especificar manualmente o local.
WARNING
Se você especificar um local de persistência de chave explícita, o sistema de proteção de dados cancela o registro a
criptografia de chave padrão no mecanismo de rest, portanto, as chaves não são criptografadas em repouso. É
recomendável que você adicionalmente especificar um mecanismo de criptografia de chave explícita para implantações de
produção.
Sistema de arquivos
Para configurar um repositório chave baseadas no sistema de arquivos, chame o PersistKeysToFileSystem
rotina de configuração, conforme mostrado abaixo. Fornecer um DirectoryInfo apontando para o repositório
em que as chaves devem ser armazenadas:
O DirectoryInfo pode apontar para um diretório no computador local, ou ele 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 exigem acesso para usar esse repositório), considere o uso de Windows
DPAPI (no Windows) para criptografar as chaves em repouso. Caso contrário, considere o uso de um
certificado x. 509 para criptografar as chaves em repouso.
O Azure e Redis
O Microsoft.AspNetCore.DataProtection.AzureStorage e
Microsoft.AspNetCore.DataProtection.StackExchangeRedis pacotes permitem armazenar chaves de proteção
de dados no armazenamento do Azure ou de um Redis cache. As chaves podem ser compartilhadas entre
várias instâncias de um aplicativo web. Aplicativos podem compartilhar cookies de autenticação ou proteção
CSRF em vários servidores.
O Microsoft.AspNetCore.DataProtection.AzureStorage e Microsoft.AspNetCore.DataProtection.Redis pacotes
permitem armazenar 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. Aplicativos podem compartilhar
cookies de autenticação ou proteção CSRF em vários servidores.
Para configurar o provedor de armazenamento de BLOBs do Azure, chame um dos
PersistKeysToAzureBlobStorage sobrecargas:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri("<blob URI including SAS token>"));
}
Registro
Só se aplica às implantações do Windows.
Às vezes, o aplicativo pode não ter acesso de gravação para o sistema de arquivos. Considere um cenário em
que um aplicativo é executado como uma conta de serviço virtual (como w3wp.exeda identidade do pool de
aplicativo). Nesses casos, o administrador pode provisionar uma chave do registro que seja acessível pela
identidade da conta de serviço. Chame o PersistKeysToRegistry método de extensão, conforme mostrado
abaixo. Fornecer um RegistryKey apontando para o local onde as chaves de criptografia devem ser
armazenadas:
IMPORTANT
É recomendável usar Windows DPAPI para criptografar as chaves em repouso.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
// using Microsoft.AspNetCore.DataProtection;
services.AddDataProtection()
.PersistKeysToDbContext<MyKeysContext>();
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using WebApp1.Data;
namespace WebApp1
{
class MyKeysContext : DbContext, IDataProtectionKeyContext
{
// A recommended constructor overload when using EF Core
// with dependency injection.
public MyKeysContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
MyKeysContext éo DbContext definido no exemplo de código anterior. Se você estiver usando um DbContext
com um nome diferente, substitua seu DbContext nome do MyKeysContext .
O DataProtectionKeys classe/entidade adota a estrutura mostrada na tabela a seguir.
O sistema de proteção de dados emprega um mecanismo de descoberta por padrão para determinar as
chaves de criptografia como devem ser criptografados em repouso. O desenvolvedor pode substituir o
mecanismo de descoberta e especificar manualmente como chaves devem ser criptografadas em repouso.
WARNING
Se você especificar um explícito local de persistência da chave, o sistema de proteção de dados cancela o registro a
criptografia de chave padrão no mecanismo de rest. Consequentemente, as chaves não são criptografadas em repouso.
É recomendável que você especificar um mecanismo de criptografia de chave explícita para implantações de produção.
As opções de mecanismo de criptografia em repouso são descritas neste tópico.
Para obter mais informações, consulte configurar a proteção de dados do ASP.NET Core:
ProtectKeysWithAzureKeyVault.
Windows DPAPI
Só se aplica às implantações do Windows.
Quando o Windows DPAPI é usado, o material da chave é criptografada com CryptProtectData antes que
estão sendo mantidos no armazenamento. A DPAPI é um mecanismo de criptografia apropriado para os
dados que nunca é lida de fora do computador atual (no entanto é possível fazer essas chaves até do Active
Directory; Consulte DPAPI e perfis móveis). Para configurar a criptografia de chave em repouso DPAPI,
chame um dos ProtectKeysWithDpapi métodos de extensão:
Se ProtectKeysWithDpapi é chamado sem parâmetros, somente a conta de usuário atual do Windows poderá
decifrar o anel de chave persistente. Opcionalmente, você pode especificar que qualquer conta de usuário no
computador (não apenas a conta de usuário atual) poderá decifrar o anel de chave:
public void ConfigureServices(IServiceCollection services)
{
// All user accounts on the machine can decrypt the keys
services.AddDataProtection()
.ProtectKeysWithDpapi(protectToLocalMachine: true);
}
Certificado X.509
Se o aplicativo é distribuído entre vários computadores, pode ser conveniente distribuir um certificado x. 509
compartilhado entre as máquinas e configure os aplicativos hospedados para usar o certificado de
criptografia de chaves em repouso:
Devido às limitações do .NET Framework, somente os certificados com chaves privadas de CAPI têm suporte.
Consulte o conteúdo abaixo para obter possíveis soluções para essas limitações.
Windows DPAPI-NG
Esse mecanismo está disponível apenas no Windows 8/Windows Server 2012 ou posterior.
Começando com o Windows 8, o sistema operacional do Windows oferece suporte a DPAPI-NG (também
chamado de CNG DPAPI). Para obter mais informações, consulte sobre o DPAPI CNG.
A entidade de segurança é codificada como uma regra de proteção do descritor. No exemplo a seguir que
chama ProtectKeysWithDpapiNG, somente o usuário de domínio com o SID especificado pode
descriptografar o anel de chave:
Também há uma sobrecarga sem parâmetros de ProtectKeysWithDpapiNG . Use esse método de conveniência
para especificar a regra "SID = {CURRENT_ACCOUNT_SID }", onde CURRENT_ACCOUNT_SID é o SID da
conta de usuário atual do Windows:
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 é executado sob sua identidade).
Criptografia baseada em certificado com o Windows DPAPI-NG
Se o aplicativo está em execução no Windows 8.1 / Windows Server 2012 R2 ou posterior, você pode usar o
Windows DPAPI-NG para executar a criptografia baseada em certificado. Use a cadeia de caracteres do
descritor de regra "certificado = HashId:THUMBPRINT", onde impressão digital é a impressão digital
codificação hexadecimal SHA1 do certificado:
Qualquer aplicativo apontado para esse repositório deve estar executando no Windows 8.1 / Windows Server
2012 R2 ou posterior para decifrar as chaves.
Depois que um objeto é persistido para o repositório 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. A
principal finalidade desse comportamento é evitar dados corrompidos.
Uma consequência deste comportamento é que, quando uma chave é gravada para o repositório 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 de algorítmicos subjacentes, material de chave mestre e a
criptografia em Propriedades do 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 entrarão
em vigor até a próxima vez que uma chave é gerada, por meio 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 algorítmicos contidas na chave do
Se você precisar dessas configurações arranque anteriores à próxima chave automática sem interrupção de tempo,
considere 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 dar tempo
para a alteração seja propagada) e a data de expiração na chamada.
TIP
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 será dependentes do aplicativo específico que
invocou as rotinas de geração de chave.
Formato de armazenamento de chaves no ASP.NET
Core
24/07/2018 • 5 minutes to read • Edit Online
Objetos são armazenados em repouso na representação XML. O diretório padrão para o armazenamento de
chaves é % LOCALAPPDATA%\ASP.NET\DataProtection-Keys.
Nesse caso, apenas a chave especificada é revogada. Se a id da chave é "*", no entanto, como mostra o exemplo
abaixo, cuja data de criação está antes da data de revogação especificado de todas as chaves são revogadas.
O <motivo > elemento nunca é lido pelo sistema. Ele é simplesmente um local conveniente para armazenar um
motivo legível por humanos para revogação.
Provedores de proteção de dados efêmero no núcleo
do ASP.NET
22/06/2018 • 2 minutes to read • Edit Online
/*
* SAMPLE OUTPUT
*
* Enter input: Hello!
* Protect returned: CfDJ8AAAAAAAAAAAAAAAAAAAAA...uGoxWLjGKtm1SkNACQ
* Unprotect returned: Hello!
* << throws CryptographicException >>
*/
Compatibilidade no ASP.NET Core
21/06/2018 • 2 minutes 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
NOTE
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.
TIP
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");
}
}
}
TIP
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.
Armazenamento seguro dos segredos do
aplicativo em desenvolvimento no ASP.NET
Core
02/02/2019 • 16 minutes to read • Edit Online
Variáveis de ambiente
Variáveis de ambiente são usadas para evitar o armazenamento de segredos do aplicativo no código
ou em arquivos de configuração local. Variáveis de ambiente substituem os valores de configuração
para todas as fontes de configuração especificado anteriormente.
Configure a leitura dos valores de variáveis de ambiente chamando AddEnvironmentVariables no
Startup construtor:
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
Configuration = builder.Build();
}
Considere um aplicativo da web ASP.NET Core no qual contas de usuário individuais segurança
está habilitada. Uma cadeia de caracteres de conexão de banco de dados padrão está incluída no
projeto do appSettings. JSON arquivo com a chave DefaultConnection . É a cadeia de caracteres de
conexão padrão para o LocalDB, que é executado no modo de usuário e não requer uma senha.
Durante a implantação de aplicativo, o DefaultConnection valor de chave pode ser substituído com o
valor da variável de ambiente. A variável de ambiente pode armazenar a cadeia de caracteres de
conexão completa com credenciais confidenciais.
WARNING
Variáveis de ambiente geralmente são armazenadas em texto não criptografado e sem formatação. Se o
computador ou processo for comprometido, as variáveis de ambiente podem ser acessadas por pessoas não
confiáveis. Medidas adicionais para evitar a divulgação de segredos do usuário podem ser necessárias.
Secret Manager
A ferramenta Secret Manager armazena dados confidenciais durante o desenvolvimento de um
projeto ASP.NET Core. Nesse contexto, uma parte dos dados confidenciais é um segredo do
aplicativo. Segredos do aplicativo são armazenados em um local separado da árvore do projeto. Os
segredos do aplicativo são compartilhados em vários projetos ou associados a um projeto específico.
Os segredos do aplicativo não são verificados no controle de origem.
WARNING
A ferramenta Secret Manager não criptografa os segredos armazenados e não deve ser tratada como um
repositório confiável. Ele é apenas a 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.
TIP
Executar dotnet --version em um shell de comando para ver o número de versão do SDK do .NET Core
instalado.
Um aviso será exibido se a ferramenta inclui o SDK do .NET Core que está sendo usada:
Instalar o secretmanager pacote do NuGet em seu projeto ASP.NET Core. Por exemplo:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<UserSecretsId>1242d6d6-9df3-4031-b031-d9b27d13c25a</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore"
Version="1.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets"
Version="1.1.2" />
<PackageReference Include="System.Data.SqlClient"
Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools"
Version="1.0.1" />
</ItemGroup>
</Project>
dotnet user-secrets -h
Options:
-?|-h|--help Show help information
--version Show version information
-v|--verbose Show verbose output
-p|--project <PROJECT> Path to project. Defaults to searching the current
directory.
-c|--configuration <CONFIGURATION> The project configuration to use. Defaults to 'Debug'.
--id The user secret ID to use.
Commands:
clear Deletes all the application secrets
list Lists all the application secrets
remove Removes the specified user secret
set Sets the user secret to the specified value
Use "dotnet user-secrets [command] --help" for more information about a command.
NOTE
Você deve estar no mesmo diretório que o . csproj arquivo para executar as ferramentas definidas na . csproj
do arquivo DotNetCliToolReference elementos.
Defina um segredo
A ferramenta Secret Manager opera em definições de configuração de específicos do projeto
armazenadas no perfil do usuário. Para usar os segredos do usuário, defina uma UserSecretsId
elemento dentro de uma PropertyGroup da . csproj arquivo. O valor de UserSecretsId é arbitrária,
mas é exclusiva para o projeto. Os desenvolvedores geralmente geram um GUID para o
UserSecretsId .
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<UserSecretsId>1242d6d6-9df3-4031-b031-d9b27d13c25a</UserSecretsId>
</PropertyGroup>
TIP
No Visual Studio, clique com botão direito no projeto no Gerenciador de soluções e selecione gerenciar
segredos do usuário no menu de contexto. Esse gesto adiciona uma UserSecretsId elemento, preenchido
com um GUID para o . csproj arquivo. O Visual Studio abre uma Secrets arquivo no editor de texto. Substitua o
conteúdo do Secrets com os pares chave-valor a ser armazenado. Por exemplo:
{
"Movies": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true",
"ServiceApiKey": "12345"
}
}
{
"Movies:ServiceApiKey": "12345"
}
Defina um segredo do aplicativo consiste em uma chave e seu valor. O segredo está associado com o
projeto UserSecretsId valor. Por exemplo, execute o seguinte comando do diretório no qual o . csproj
arquivo existe:
No exemplo anterior, os dois-pontos indica que Movies é um objeto literal com um ServiceApiKey
propriedade.
A ferramenta Secret Manager pode ser usada de outros diretórios muito. Use o --project opção de
fornecer o caminho do sistema de arquivos no qual o . csproj arquivo existe. Por exemplo:
Acesse um segredo
O API de configuração do ASP.NET Core fornece acesso aos segredos Secret Manager. Se seu
projeto direcionado ao .NET Framework, instale o Microsoft.Extensions.Configuration.UserSecrets
pacote do NuGet.
No ASP.NET Core 2.0 ou posterior, a fonte de configuração de segredos do usuário é adicionada
automaticamente no modo de desenvolvimento quando o projeto chama CreateDefaultBuilder para
inicializar uma nova instância do host com os padrões pré-configurados. CreateDefaultBuilder
chamadas AddUserSecrets quando o EnvironmentName é Development:
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
Configuration = builder.Build();
}
O API de configuração do ASP.NET Core fornece acesso aos segredos Secret Manager. Instalar o
Microsoft.Extensions.Configuration.UserSecrets pacote do NuGet.
Adicionar a fonte de configuração de segredos do usuário com uma chamada para AddUserSecrets
no Startup construtor:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json",
optional: false,
reloadOnChange: true)
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
Configuration = builder.Build();
}
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
Configuration = builder.Build();
}
{
"Movies": {
"ServiceApiKey": "12345",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Para mapear os segredos anteriores para um POCO, use o Configuration da API de associação do
grafo de objeto recurso. O código a seguir associa a um personalizado MovieSettings POCO e acessa
o ServiceApiKey valor da propriedade:
{
"ConnectionStrings": {
"Movies": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;User
Id=johndoe;Password=pass123;MultipleActiveResultSets=true"
}
}
Uma abordagem mais segura é armazenar a senha como um segredo. Por exemplo:
Remover o Password par chave-valor da cadeia de conexão na appSettings. JSON. Por exemplo:
{
"ConnectionStrings": {
"Movies": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;User
Id=johndoe;MultipleActiveResultSets=true"
}
}
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
Configuration = builder.Build();
}
Lista os segredos
Suponha que o aplicativo Secrets arquivo contém os dois segredos do seguintes:
{
"Movies": {
"ServiceApiKey": "12345",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
No exemplo anterior, dois-pontos em nomes de chave denota a hierarquia de objetos dentro Secrets.
{
"Movies": {
"ServiceApiKey": "12345",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
O aplicativo Secrets arquivo foi modificado para remover o par chave-valor associado a
MoviesConnectionString chave:
{
"Movies": {
"ServiceApiKey": "12345"
}
}
Movies:ServiceApiKey = 12345
{
"Movies": {
"ServiceApiKey": "12345",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Todos os segredos do usuário para o aplicativo tem sido excluídos do Secrets arquivo:
{}
Recursos adicionais
Configuração no ASP.NET Core
Provedor de configuração do Cofre de chaves do Azure no ASP.NET Core
Impor HTTPS no ASP.NET Core
08/01/2019 • 17 minutes to read • Edit Online
WARNING
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. Os clientes da API não podem
compreender ou obedecem redirecionamentos de HTTP para HTTPS. Esses clientes podem enviar informações por meio
de HTTP. As APIs da Web deverá:
Não realizar a escuta em HTTP.
Feche a conexão com o código de status 400 (solicitação incorreta) e não atender à solicitação.
Exigir HTTPS
É recomendável que produção do ASP.NET Core chamada de aplicativos web:
Middleware de redirecionamento de HTTPS (UseHttpsRedirection) para redirecionar solicitações HTTP para
HTTPS.
Middleware HSTS (UseHsts) para enviar cabeçalhos de protocolo de segurança de transporte estrito (HSTS )
HTTP aos clientes.
NOTE
Aplicativos implantados em uma configuração de proxy reverso permitem que o proxy lidar com a segurança de conexão
(HTTPS). Se o proxy também manipula o redirecionamento de HTTPS, não é necessário usar o Middleware de
redirecionamento de HTTPS. Se o servidor proxy também manipula a HSTS cabeçalhos de gravação (por exemplo, dar
suporte a HSTS nativos no IIS 10.0 (1709) ou posterior), Middleware HSTS não é necessário para o aplicativo. Para obter
mais informações, consulte Opt-out de HTTPS/HSTS na criação do projeto.
UseHttpsRedirection
O código a seguir chama UseHttpsRedirection no Startup classe:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
Indicar uma porta com o esquema segura usando o ASPNETCORE_URLS variável de ambiente. A variável de
ambiente configura o servidor. O middleware indiretamente descobre a porta HTTPS por meio de
IServerAddressesFeature. Essa abordagem não funciona em implantações de proxy reverso.
No desenvolvimento, defina uma URL HTTPS launchsettings. JSON. Habilite HTTPS quando o IIS
Express é usado.
Configurar um ponto de extremidade de URL HTTPS para uma implantação de borda para o público do
Kestrel server ou HTTP. sys server. Somente uma porta HTTPS é usado pelo aplicativo. O middleware
descobre a porta por meio de IServerAddressesFeature.
NOTE
Quando um aplicativo é executado em uma configuração de proxy reverso, IServerAddressesFeature não está disponível.
Defina a porta usando uma das outras abordagens descritas nesta seção.
Quando o Kestrel ou HTTP. sys é usado como um servidor de borda para o público, o Kestrel ou HTTP. sys
deve ser configurado para escutar em ambos:
A porta segura em que o cliente é redirecionado (normalmente, 443 em 5001 no desenvolvimento e
produção).
A porta não segura (normalmente, 80 na produção) e 5000 no desenvolvimento.
A porta não segura deve estar acessível pelo cliente para que o aplicativo para receber uma solicitação não
segura e redirecionar o cliente para a porta segura.
Para obter mais informações, consulte configuração de ponto de extremidade do Kestrel ou Implementação do
servidor Web HTTP.sys no ASP.NET Core.
Cenários de implantação
Qualquer firewall entre o cliente e o servidor também deve ter as portas de comunicação aberto para o tráfego.
Se as solicitações são encaminhadas em uma configuração de proxy reverso, use Middleware de cabeçalhos
encaminhados antes de chamar Middleware de redirecionamento de HTTPS. Encaminhado atualizações de
Middleware de cabeçalhos a Request.Scheme , usando o X-Forwarded-Proto cabeçalho. O middleware permite
redirecionar URIs e outras políticas de segurança para funcionar corretamente. Quando o Middleware de
cabeçalhos encaminhados não for usado, o aplicativo de back-end não pode receber o esquema correto e
acabar em um loop de redirecionamento. Uma mensagem de erro comuns do usuário final é que muitos
redirecionamentos ocorreram.
Ao implantar o serviço de aplicativo do Azure, siga as orientações em Tutorial: vincular um certificado SSL
personalizado ao serviço Aplicativos Web do Azure.
Opções
O seguinte realçado código chama AddHttpsRedirection para configurar as opções de middleware:
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(60);
options.ExcludedHosts.Add("example.com");
options.ExcludedHosts.Add("www.example.com");
});
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
options.HttpsPort = 5001;
});
}
O código realçado anterior requer que todas as solicitações usem HTTPS ; portanto, as solicitações HTTP são
ignoradas. O seguinte código realçado redireciona todas as solicitações HTTP para HTTPS:
app.UseRewriter(options);
Para obter mais informações, consulte Middleware de reconfiguração de URL. O middleware também permite
que o aplicativo para definir o código de status ou o código de status e a porta quando o redirecionamento é
executado.
Exigir HTTPS globalmente ( options.Filters.Add(new RequireHttpsAttribute()); ) é uma prática recomendada de
segurança. Aplicando o [RequireHttps] atributo a todas as páginas do Razor/controladores não é considerado
tão seguro quanto exigir HTTPS globalmente. Você não pode garantir o [RequireHttps] atributo é aplicado
quando novos controladores e páginas Razor são adicionadas.
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(60);
options.ExcludedHosts.Add("example.com");
options.ExcludedHosts.Add("www.example.com");
});
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
options.HttpsPort = 5001;
});
}
ASP.NET Core
------------
Successfully installed the ASP.NET Core HTTPS Development Certificate.
To trust the certificate run 'dotnet dev-certs https --trust' (Windows and macOS only).
For establishing trust on other platforms refer to the platform specific documentation.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
Instalar o SDK do .NET Core instala o certificado de desenvolvimento do ASP.NET Core HTTPS para o
repositório de certificados de usuário local. O certificado foi instalado, mas não for confiável. Confiar em
certificado execute a etapa única para executar o dotnet dev-certs ferramenta:
Informações adicionais
Configure o ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga
Hospede o ASP.NET Core no Linux com o Apache: Configuração HTTPS
Hospede o ASP.NET Core no Linux com Nginx: Configuração HTTPS
Como configurar o SSL no IIS
Suporte a navegador HSTS OWASP
Suporte da UE Data Protection GDPR
(regulamento geral) no ASP.NET Core
26/01/2019 • 9 minutes to read • Edit Online
// This method gets called by the runtime. Use this method to add services
// to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies
// is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the
// HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
// If the app uses session state, call Session Middleware after Cookie
// Policy Middleware and before MVC Middleware.
// app.UseSession();
app.UseMvc();
}
}
// This method gets called by the runtime. Use this method to add services
// to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies
// is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the
// HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
// If the app uses session state, call Session Middleware after Cookie
// Policy Middleware and before MVC Middleware.
// app.UseSession();
app.UseMvc();
}
}
@{
var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
var showBanner = !consentFeature?.CanTrack ?? false;
var cookieString = consentFeature?.CreateConsentCookie();
}
@if (showBanner)
{
<nav id="cookieConsent" class="navbar navbar-default navbar-fixed-top" role="alert">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-
target="#cookieConsent .navbar-collapse">
<span class="sr-only">Toggle cookie consent banner</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="navbar-brand"><span class="glyphicon glyphicon-info-sign" aria-
hidden="true"></span></span>
</div>
<div class="collapse navbar-collapse">
<p class="navbar-text">
Use this space to summarize your privacy and cookie use policy.
</p>
<div class="navbar-right">
<a asp-page="/Privacy" class="btn btn-info navbar-btn">Learn More</a>
<button type="button" class="btn btn-default navbar-btn" data-cookie-
string="@cookieString">Accept</button>
</div>
</div>
</div>
</nav>
<script>
(function () {
document.querySelector("#cookieConsent button[data-cookie-
string]").addEventListener("click", function (el) {
document.cookie = el.target.dataset.cookieString;
document.querySelector("#cookieConsent").classList.add("hidden");
}, false);
})();
</script>
}
Essa parcial:
Obtém o estado do controle para o usuário. Se o aplicativo estiver configurado para exigir o
consentimento, o usuário deve consentir antes de cookies podem ser controlados. Se for necessário o
consentimento, o painel de consentimento do cookie é fixa na parte superior da barra de navegação
criada pelo layout. cshtml arquivo.
Fornece um HTML <p> elemento para resumir a sua privacidade e cookie usar a política.
Fornece um link para a página de privacidade ou exibição onde você pode detalhar a política de
privacidade do seu site.
Cookies essenciais
Se o consentimento não foi fornecido, somente os cookies marcados essenciais são enviados para o
navegador. O código a seguir faz com que um cookie essenciais:
public IActionResult OnPostCreateEssentialAsync()
{
HttpContext.Response.Cookies.Append(Constants.EssentialSec,
DateTime.Now.Second.ToString(),
new CookieOptions() { IsEssential = true });
ResponseCookies = Response.Headers[HeaderNames.SetCookie].ToString();
return RedirectToPage("./Index");
}
Estado de sessão cookies não são essenciais. Estado de sessão não está funcionando quando o rastreamento
está desabilitado.
Dados pessoais
Aplicativos ASP.NET Core criados com contas de usuário individuais incluem código para baixar e excluir
dados pessoais.
Selecione o nome de usuário e, em seguida, selecione dados pessoais:
Notas:
Para gerar a Account/Manage de código, consulte Scaffold identidade.
O exclua e baixar links atuam somente em dados de identidade padrão. Aplicativos que criam os dados
de usuário personalizada devem ser estendidos para exclusão/baixar os dados de usuário personalizada.
Para obter mais informações, consulte adicionar, baixar e excluir dados de usuário personalizada para
identidade.
Salva os tokens para o usuário que são armazenados na tabela de banco de dados de identidade
AspNetUserTokens são excluídos quando o usuário é excluído por meio do comportamento de exclusão
em cascata devido ao chave estrangeira.
Autenticação de provedor externo, como Facebook e Google, não está disponível antes da política de
cookie é aceito.
Criptografia em repouso
Alguns bancos de dados e mecanismos de armazenamento permitem a criptografia em repouso.
Criptografia em repouso:
Criptografa dados armazenados automaticamente.
Criptografa sem configuração, programação ou outro trabalho para o software que acessa os dados.
É a opção mais segura e fácil.
Permite que o banco de dados gerenciar chaves e criptografia.
Por exemplo:
Microsoft SQL e SQL Azure fornecem Transparent Data Encryption (TDE ).
SQL Azure criptografa o banco de dados por padrão
Blobs, arquivos, tabela e o armazenamento de filas do Azure são criptografados por padrão.
Para bancos de dados que não fornecem internos de criptografia em repouso, você poderá usar a
criptografia de disco para fornecer a mesma proteção. Por exemplo:
BitLocker para Windows Server
Linux:
eCryptfs
EncFS.
Recursos adicionais
Microsoft.com/GDPR
Provedor de configuração do Cofre de chaves do
Azure no ASP.NET Core
12/02/2019 • 28 minutes to read • Edit Online
Pacotes
Para usar o provedor de configuração do Cofre de chave do Azure, adicione uma referência de pacote para o
Microsoft.Extensions.Configuration.AzureKeyVault pacote.
Para adotar a identidades para recursos do Azure gerenciadas cenário, adicione uma referência de pacote para
o Microsoft.Azure.Services.AppAuthentication pacote.
NOTE
No momento da gravação, a versão estável mais recente do Microsoft.Azure.Services.AppAuthentication , versão
1.0.3 , fornece suporte para atribuído pelo sistema de identidades gerenciadas. Suporte para atribuída ao usuário
identidades gerenciadas está disponível no 1.0.2-preview pacote. Este tópico demonstra o uso de identidades
gerenciadas pelo sistema, e o aplicativo de exemplo fornecido usa a versão 1.0.3 do
Microsoft.Azure.Services.AppAuthentication pacote.
Aplicativo de exemplo
O aplicativo de exemplo é executado em qualquer um dos dois modos, determinados pelo #define instrução
na parte superior dos Program.cs arquivo:
Basic – Demonstra o uso de uma ID do aplicativo do Azure Key Vault e a senha (segredo do cliente) para
acessar os segredos armazenados no cofre de chaves. Implantar o Basic versão de exemplo em qualquer
host capaz de atender a um aplicativo ASP.NET Core. Siga as orientações na ID do aplicativo de uso e o
segredo do cliente para aplicativos do Azure não hospedados seção.
Managed – Demonstra como usar identidades para recursos do Azure gerenciadas para autenticar o
aplicativo ao Azure Key Vault com autenticação do Azure AD sem credenciais armazenadas no código ou
na configuração do aplicativo. Ao usar identidades gerenciadas para autenticar, uma ID de aplicativo do
Azure AD e a senha (segredo do cliente) não é necessário. O Managed versão deste exemplo deve ser
implantado no Azure. Siga as orientações na usar as identidades de gerenciado para recursos do Azure
seção.
Para obter mais informações sobre como configurar um aplicativo de exemplo usando diretivas de pré-
processador ( #define ), consulte Introdução ao ASP.NET Core.
<PropertyGroup>
<UserSecretsId>{GUID}</UserSecretsId>
</PropertyGroup>
Segredos são criados como pares nome-valor. Valores hierárquicos (seções de configuração) usam um :
(dois-pontos) como um separador no configuração do ASP.NET Core nomes de chave.
O Gerenciador de segredo é usado em um shell de comando aberto para a raiz do conteúdo do projeto, onde
{SECRET NAME} é o nome e {SECRET VALUE} é o valor:
Execute os seguintes comandos em um shell de comando na raiz do conteúdo do projeto para definir os
segredos para o aplicativo de exemplo:
Quando esses segredos são armazenados no Azure Key Vault na armazenamento secreto no ambiente de
produção com o Azure Key Vault seção, o _dev sufixo é alterado para _prod . O sufixo fornece uma indicação
visual na saída do aplicativo que indica a origem dos valores de configuração.
4. Criar um cofre de chaves no grupo de recursos com o comando a seguir, onde {KEY VAULT NAME} éo
nome para o novo cofre de chaves e {LOCATION} é a região do Azure (datacenter):
az keyvault create --name "{KEY VAULT NAME}" --resource-group "{RESOURCE GROUP NAME}" --location
{LOCATION}
az keyvault secret set --vault-name "{KEY VAULT NAME}" --name "SecretName" --value
"secret_value_1_prod"
az keyvault secret set --vault-name "{KEY VAULT NAME}" --name "Section--SecretName" --value
"secret_value_2_prod"
NOTE
Embora haja suporte para usar uma ID do aplicativo e a senha (segredo do cliente) para aplicativos hospedados no
Azure, recomendamos o uso identidades para recursos do Azure gerenciadas ao hospedar um aplicativo no Azure.
Identidades gerenciadas exigem armazenamento de credenciais no aplicativo ou sua configuração, portanto, ele é
considerado como uma abordagem mais segura em geral.
O aplicativo de exemplo usa uma ID do aplicativo e a senha (segredo do cliente) quando o #define instrução
na parte superior dos Program.cs arquivo é definido como Basic .
1. Registrar o aplicativo com o Azure AD e estabeleça uma senha (segredo do cliente) para a identidade do
aplicativo.
2. Store o nome do Cofre de chaves, a ID do aplicativo e a senha/segredo do cliente do aplicativo appSettings.
JSON arquivo.
3. Navegue até cofres de chaves no portal do Azure.
4. Selecione o Cofre de chaves que você criou na armazenamento secreto no ambiente de produção com o
Azure Key Vault seção.
5. Selecione políticas de acesso.
6. Selecione adicionar novo.
7. Selecione selecionar entidade e selecione o aplicativo registrado por nome. Selecione o selecionar
botão.
8. Abra permissões do segredo e forneça o aplicativo com obter e lista permissões.
9. Selecione OK.
10. Selecione Salvar.
11. Implante o aplicativo.
O Basic aplicativo de exemplo obtém seus valores de configuração de IConfigurationRoot com o mesmo
nome que o nome do segredo:
Valores não são hierárquicos: O valor para SecretName é obtido com config["SecretName"] .
Valores hierárquicos (seções): Use : notação (dois-pontos) ou o GetSection método de extensão. Use
qualquer uma dessas abordagens para obter o valor de configuração:
config["Section:SecretName"]
config.GetSection("Section")["SecretName"]
O aplicativo chama AddAzureKeyVault com os valores fornecidos pelo appSettings. JSON arquivo:
// using Microsoft.Extensions.Configuration;
config.AddAzureKeyVault(
$"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
builtConfig["AzureADApplicationId"],
builtConfig["AzureADPassword"]);
}
})
.UseStartup<Startup>();
Valores de exemplo:
Nome do Cofre de chaves: contosovault
ID do aplicativo: 627e911e-43cc-61d4-992e-12db9c81b413
Senha: g58K3dtg59o1Pa+e59v2Tx829w6VxTB2yv9sv/101di=
appsettings.json:
{
"KeyVaultName": "Key Vault Name",
"AzureADApplicationId": "Azure AD Application ID",
"AzureADPassword": "Azure AD Password/Client Secret"
}
Quando você executa o aplicativo, uma página da Web mostra os valores secretos carregados. No ambiente de
desenvolvimento, os valores secretos carregar com o _dev sufixo. No ambiente de produção, os valores de
carga com o _prod sufixo.
az keyvault set-policy --name '{KEY VAULT NAME}' --object-id {OBJECT ID} --secret-permissions get list
config.AddAzureKeyVault(
$"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
keyVaultClient,
new DefaultKeyVaultSecretManager());
}
})
.UseStartup<Startup>();
Quando você executa o aplicativo, uma página da Web mostra os valores secretos carregados. No ambiente de
desenvolvimento, os valores do segredo têm o _dev sufixo porque elas são fornecidas por segredos do
usuário. No ambiente de produção, os valores de carga com o _prod sufixo porque elas são fornecidas pelo
Azure Key Vault.
Se você receber um Access denied erro, confirme se o aplicativo está registrado com o Azure AD e recebe
acesso ao Cofre de chaves. Confirme que você tiver reiniciado o serviço no Azure.
WARNING
Não usar prefixos de segredos do Cofre de chaves para colocar segredos para vários aplicativos no mesmo Cofre de
chaves ou para colocar segredos ambientais (por exemplo, development versus produção segredos) no mesmo cofre. É
recomendável que diferentes aplicativos e ambientes de desenvolvimento/produção usam cofres de chaves separados
para isolar os ambientes de aplicativo para o nível mais alto de segurança.
No exemplo a seguir, um segredo é estabelecido na chave do cofre (e usar a ferramenta Secret Manager no
ambiente de desenvolvimento) para 5000-AppSecret (períodos não são permitidos em nomes de segredos do
Cofre de chaves). Esse segredo representa um segredo do aplicativo para versão Version=5.0.0.0 do aplicativo.
Para outra versão do aplicativo, 5.1.0.0, um segredo é adicionado à chave de cofre (e usar a ferramenta Secret
Manager) para 5100-AppSecret . Cada versão do aplicativo carrega seu valor com controle de versão do
segredo em sua configuração como AppSecret , remoção desativar a versão ele carrega o segredo.
AddAzureKeyVault é chamado com um personalizado IKeyVaultSecretManager :
// using Microsoft.Extensions.Configuration;
config.AddAzureKeyVault(
$"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
builtConfig["AzureADApplicationId"],
builtConfig["AzureADPassword"],
new PrefixKeyVaultSecretManager(versionPrefix));
}
})
.UseStartup<Startup>();
Os valores para nome do Cofre de chaves, a ID do aplicativo e a senha (segredo do cliente) são fornecidos pela
appSettings. JSON arquivo:
{
"KeyVaultName": "Key Vault Name",
"AzureADApplicationId": "Azure AD Application ID",
"AzureADPassword": "Azure AD Password/Client Secret"
}
Valores de exemplo:
Nome do Cofre de chaves: contosovault
ID do aplicativo: 627e911e-43cc-61d4-992e-12db9c81b413
Senha: g58K3dtg59o1Pa+e59v2Tx829w6VxTB2yv9sv/101di=
O IKeyVaultSecretManager implementação reage aos prefixos de segredos para carregar o segredo apropriado
na configuração de versão:
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 encontrar
aqueles 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 carregamento na configuração do aplicativo
pares nome-valor.
Quando esse método é implementado:
1. A versão do aplicativo especificada no arquivo de projeto do aplicativo. No exemplo a seguir, a versão
do aplicativo é definida como 5.0.0.0 :
<PropertyGroup>
<Version>5.0.0.0</Version>
</PropertyGroup>
<PropertyGroup>
<UserSecretsId>{GUID}</UserSecretsId>
</PropertyGroup>
3. Segredos são salvos no Azure Key Vault usando os seguintes comandos de CLI do Azure:
az keyvault secret set --vault-name "{KEY VAULT NAME}" --name "5000-AppSecret" --value
"5.0.0.0_secret_value_prod"
az keyvault secret set --vault-name "{KEY VAULT NAME}" --name "5100-AppSecret" --value
"5.1.0.0_secret_value_prod"
4. Quando o aplicativo é executado, os segredos do Cofre de chaves são carregados. O segredo de cadeia
de caracteres para 5000-AppSecret corresponde à versão do aplicativo especificada no arquivo de
projeto do aplicativo ( 5.0.0.0 ).
5. A versão 5000 (com o traço) é removida do nome de chave. Em todo o aplicativo, lendo a configuração
com a chave AppSecret carrega o valor do segredo.
6. Se a versão do aplicativo é alterada no arquivo de projeto para 5.1.0.0 e o aplicativo é executado
novamente, é o valor do segredo retornado 5.1.0.0_secret_value_dev no ambiente de desenvolvimento
e 5.1.0.0_secret_value_prod em produção.
NOTE
Você também pode fornecer seus próprios KeyVaultClient implementação para AddAzureKeyVault . Compartilhando
uma única instância do cliente entre o aplicativo permite que um cliente personalizado.
config.AddAzureKeyVault(
builtConfig["KeyVaultName"],
builtConfig["AzureADApplicationId"],
cert.OfType<X509Certificate2>().Single(),
new EnvironmentSecretManager(context.HostingEnvironment.ApplicationName));
store.Close();
Chaves do Azure Key Vault não podem usar dois-pontos como um separador. A abordagem descrita neste
tópico usa double traços ( -- ) como separador de valores hierárquicos (seções). As chaves de matriz são
armazenadas no Azure Key Vault com double traços e segmentos de chave numéricos ( --0-- , --1-- ,...
--{n}-- ).
Examine o seguinte Serilog fornecida por um arquivo JSON de configuração do provedor de log. Há dois
objetos literais definidos na WriteTo matriz que refletem os dois Serilog coletores, que descrevem os destinos
para saída de log:
"Serilog": {
"WriteTo": [
{
"Name": "AzureTableStorage",
"Args": {
"storageTableName": "logs",
"connectionString": "DefaultEnd...ountKey=Eby8...GMGw=="
}
},
{
"Name": "AzureDocumentDB",
"Args": {
"endpointUrl": "https://contoso.documents.azure.com:443",
"authorizationKey": "Eby8...GMGw=="
}
}
]
}
A configuração mostrada no arquivo JSON anterior é armazenada no Azure Key Vault usando o traço duplo (
-- ) segmentos notação e numérico:
CHAVE VALOR
Serilog--WriteTo--0--Name AzureTableStorage
Serilog--WriteTo--0--Args--storageTableName logs
Serilog--WriteTo--0--Args--connectionString DefaultEnd...ountKey=Eby8...GMGw==
Serilog--WriteTo--1--Name AzureDocumentDB
Serilog--WriteTo--1--Args--endpointUrl https://contoso.documents.azure.com:443
Serilog--WriteTo--1--Args--authorizationKey Eby8...GMGw==
Recarregue os segredos
Segredos são armazenados em cache até IConfigurationRoot.Reload() é chamado. Expirado, desabilitado, e
atualizados segredos no cofre de chaves não serão respeitados pelo aplicativo até Reload é executado.
Configuration.Reload();
Recursos adicionais
Configuração no ASP.NET Core
Microsoft Azure: Cofre de chaves
Microsoft Azure: Documentação do Key Vault
Como gerar e transferir protegida por HSM chaves para o Azure Key Vault
Classe KeyVaultClient
Ataques de evitar entre solicitação intersite
forjada (CSRF/XSRF) no ASP.NET Core
02/02/2019 • 27 minutes to read • Edit Online
Observe que o formulário action postagens para o site vulnerável, não para o site mal-
intencionado. Essa é a parte de "site cruzado" de CSRF.
3. O usuário seleciona o botão Enviar. O navegador faz a solicitação e inclui automaticamente o cookie
de autenticação para o domínio solicitado, www.good-banking-site.com .
4. A solicitação é executada www.good-banking-site.com server com o contexto de autenticação do
usuário e pode executar qualquer ação que um usuário autenticado tem permissão para executar.
Além do cenário em que o usuário seleciona o botão para enviar o formulário, o site mal-intencionado
pode:
Execute um script que envia automaticamente o formulário.
Envie o envio do formulário como uma solicitação AJAX.
Oculte o formulário usando CSS.
Esses cenários alternativos não exigem qualquer entrada do usuário que não sejam inicialmente visitar o
site mal-intencionado ou ação.
Usar o HTTPS não impede que um ataque CSRF. O site mal-intencionado pode enviar um
https://www.good-banking-site.com/ solicitar tão fácil quanto ele pode enviar uma solicitação não segura.
Alguns ataques são direcionados a pontos de extremidade que respondem às solicitações GET, neste caso,
uma marca de imagem pode ser usada para executar a ação. Essa forma de ataque é comum nos sites de
Fórum que permitem imagens, mas bloqueiam o JavaScript. Aplicativos que alteram o estado em
solicitações GET, em que as variáveis ou os recursos são alterados, são vulneráveis a ataques mal-
intencionados. Solicitações GET que alteram o estado são inseguras. Uma prática recomendada é
nunca alterar o estado em uma solicitação GET.
Ataques de CSRF são possíveis em relação a aplicativos web que usam cookies para autenticação porque:
Os navegadores armazenam os cookies emitidos por um aplicativo web.
Armazenado cookies incluem cookies de sessão para usuários autenticados.
Os navegadores enviam a que todos os cookies associados com um domínio para o aplicativo web cada
solicitação, independentemente de como a solicitação para o aplicativo foi gerada dentro do navegador.
No entanto, os ataques de CSRF não estão limitadas a exploração de cookies. Por exemplo, a autenticação
básica e Digest também são vulneráveis. Depois que um usuário entra com a autenticação básica ou Digest,
o navegador automaticamente envia as credenciais até que a sessão† termina.
†Nesse contexto, sessão refere-se à sessão do lado do cliente durante o qual o usuário é autenticado. Ele é
não relacionada a sessões do lado do servidor ou Middleware de sessão do ASP.NET Core.
Os usuários podem se proteger contra vulnerabilidades CSRF tomando precauções:
Entrar em aplicativos web quando terminar de usá-los.
Limpar os cookies do navegador periodicamente.
No entanto, as vulnerabilidades CSRF são basicamente um problema com o aplicativo web, não pelo
usuário final.
<form method="post">
...
</form>
Similarily, IHtmlHelper.BeginForm gera tokens antifalsificação por padrão, se o método do formulário não
é GET.
A geração automática de tokens antifalsificação para elementos de formulário HTML acontece quando o
<form> marca contém o method="post" atributo e uma das opções a seguir forem verdadeira:
Geração automática de tokens antifalsificação para elementos de formulário HTML pode ser desabilitada:
Desabilitar explicitamente tokens antifalsificação com o asp-antiforgery atributo:
O elemento de formulário é aceito-out dos auxiliares de marca usando o auxiliar de marca ! recusar
símbolo:
<!form method="post">
...
</!form>
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper,
Microsoft.AspNetCore.Mvc.TagHelpers
NOTE
Páginas do 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 do sincronizador
(STP ). STP é usado quando o usuário solicita uma página com os dados de formulário:
1. O servidor envia um token associado com a atual identidade do usuário 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 à identidade do usuário autenticado, a solicitação
será rejeitada.
O token é exclusivo e imprevisíveis. O token também pode ser usado para garantir que 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 ASP.NET Core MVC e páginas do Razor geram
tokens contra falsificação. O seguinte par de exemplos do modo de exibição gera tokens contra falsificação:
Adicionar explicitamente um token antifalsificação para um <form> elemento sem o uso de auxiliares de
marca com o auxiliar HTML @Html.AntiForgeryToken :
Em cada um dos casos anteriores, o ASP.NET Core adiciona um campo de formulário oculto semelhante ao
seguinte:
ASP.NET Core inclui três filtros para trabalhar com tokens antifalsificação:
ValidateAntiForgeryToken
AutoValidateAntiforgeryToken
IgnoreAntiforgeryToken
Opções de antifalsificação
Personalize opções antifalsificação em Startup.ConfigureServices :
services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
OPÇÃO DESCRIÇÃO
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);
});
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
ManageMessageId? message = ManageMessageId.Error;
var user = await GetCurrentUserAsync();
if (user != null)
{
var result =
await _userManager.RemoveLoginAsync(
user, account.LoginProvider, account.ProviderKey);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
}
O ValidateAntiForgeryToken atributo requer um token para solicitações para os métodos de ação decora,
incluindo as solicitações HTTP GET. Se o ValidateAntiForgeryToken atributo é aplicado em controladores
do aplicativo, ele pode ser substituído com o IgnoreAntiforgeryToken atributo.
NOTE
ASP.NET Core não dá suporte a adição de tokens antifalsificação para solicitações GET automaticamente.
Automaticamente validar tokens antifalsificação para métodos HTTP não seguros apenas
Aplicativos ASP.NET Core não geram tokens contra falsificação para métodos HTTP seguros (GET, HEAD,
opções e rastreamento). Em vez de aplicar amplamente a ValidateAntiForgeryToken atributo e, em seguida,
substituindo-o com IgnoreAntiforgeryToken atributos, o AutoValidateAntiforgeryToken atributo pode ser
usado. Esse atributo funciona de maneira idêntica ao ValidateAntiForgeryToken de atributo, exceto que ele
não exige tokens para solicitações feitas usando os seguintes métodos HTTP:
OBTER
HOME
OPÇÕES
TRACE
É recomendável 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
antifalsificação por padrão, a menos que ValidateAntiForgeryToken é aplicado a métodos de ação
individual. Ele tem mais provavelmente neste cenário para um método de ação a ser deixado POST
desprotegidos por engano, deixando o aplicativo vulnerável a ataques CSRF. Todas as postagens devem
enviar o token antifalsificação.
APIs não têm um mecanismo automático para enviar a parte não-cookie 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 definir cookies a partir do servidor ou lê-
los a partir do cliente.
O exemplo anterior usa JavaScript para ler o valor do campo oculto para o cabeçalho de POSTAGEM de
AJAX.
JavaScript pode também tokens de acesso 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 para enviar o token em um cabeçalho chamado X-CSRF-TOKEN , configure o
serviço antifalsificação 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 a um cabeçalho quando ele envia uma
solicitação ao servidor. Esse processo é automático. O cabeçalho não precisa ser definido explicitamente no
cliente. O nome do cabeçalho é X-XSRF-TOKEN . O servidor deve detectar esse cabeçalho e validar seu
conteúdo.
API do ASP.NET Core trabalhar com essa convenção na inicialização do seu aplicativo:
Configurar seu aplicativo para fornecer um token em um cookie chamado XSRF-TOKEN .
Configurar o serviço antifalsificação para procurar por um cabeçalho chamado X-XSRF-TOKEN .
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
string path = context.Request.Path.Value;
if (
string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
// The request token can be sent as a JavaScript-readable cookie,
// and Angular uses it by default.
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
}
Estender antifalsificação
O IAntiForgeryAdditionalDataProvider tipo permite aos desenvolvedores estender o comportamento do
sistema anti-CSRF por dados adicionais de ciclo completo em cada token. O GetAdditionalData método é
chamado sempre que um token de campo é gerado e o valor de retorno é incorporado dentro do token
gerado. Um implementador poderia retornar um carimbo de hora, um nonce ou qualquer outro valor e, em
seguida, chame ValidateAdditionalData para validar dados quando o token é validado. Nome de usuário do
cliente já está incorporado em tokens gerados, portanto, não há nenhuma 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 Web Application Security Project (OWASP ).
Hospedar o ASP.NET Core em um web farm
Impedir ataques de redirecionamento aberto no
ASP.NET Core
23/08/2018 • 6 minutes 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 cadeia de consulta potencialmente pode ser violado para redirecionar usuários para uma URL
externa e mal-intencionado. Essa violação é chamado de um ataque de redirecionamento aberto.
Sempre que a lógica do aplicativo redireciona para uma URL especificada, verifique se a URL de redirecionamento
não foi adulterada. O ASP.NET Core 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 para testar a antes de redirecionar URLs:
O exemplo a seguir mostra como verificar se uma URL é local antes de redirecionar.
O IsLocalUrl método protege os usuários contra inadvertidamente sendo redirecionado para um site mal-
intencionado. Você pode registrar os detalhes da URL que foi fornecido quando uma URL de local não é fornecida
em uma situação em que você esperava uma URL local. URLs de redirecionamento de registro em log podem
ajudar no diagnóstico de ataques de redirecionamento.
Evitar Cross-Site Scripting (XSS) no ASP.NET Core
10/10/2018 • 13 minutes 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 de XSS, ou seja, <, "e >. Examinar o código-fonte mostra a saída renderizada codificada como:
<"123">
WARNING
ASP.NET Core 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 de 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>
WARNING
Não concatene entradas não confiáveis em JavaScript para criar elementos DOM. Você deve usar createElement() e
atribuir valores de propriedade adequadamente, como node.TextContent= , ou use element.SetAttribute() /
element[attribute]= caso contrário, você se exporá a XSS baseado em DOM.
WARNING
Não use entradas não confiáveis 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 de usam uma lista segura limitada ao intervalo Unicode de Latim básico e codificar
todos os caracteres fora do intervalo como seus equivalentes de código de caractere. Esse comportamento
também afeta a renderização TagHelper Razor e HtmlHelper como ele utilizará os codificadores para suas cadeias
de caracteres de saída.
O raciocínio por trás disso é proteger contra bugs no navegador desconhecido ou futuras (bugs no navegador
anterior tiveram atropelados análise com base no processamento de caracteres do inglês). Se seu site da web 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 seguro de codificador para incluir Unicode intervalos apropriadas para seu
aplicativo durante a inicialização, no ConfigureServices() .
Por exemplo, usando a configuração padrão que você pode usar um HtmlHelper Razor assim;
Ampliar os caracteres tratados como seguro pelo codificador você inseriria a linha a seguir para o
ConfigureServices() método no 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 Unicode. A saída renderizada
tornaria
Intervalos de lista segura são especificados como gráficos de código Unicode, não os idiomas. O padrão Unicode
tem uma lista de gráficos de código você pode usar para localizar o gráfico que contém os caracteres. Cada
codificador, Html, JavaScript e a Url, deve ser configurado separadamente.
NOTE
Personalização da lista de confiáveis afeta apenas os codificadores originados por meio da DI. 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.
Mesma origem
Duas URLs têm a mesma origem se eles têm esquemas idênticas, hosts e portas ( 6454 RFC ).
Essas duas URLs têm a mesma origem:
https://example.com/foo.html
https://example.com/bar.html
NOTE
Internet Explorer não considera a porta ao comparar as origens.
Habilitar o CORS
Depois de registrar os serviços do CORS, use qualquer uma das abordagens a seguir para habilitar o CORS em
um aplicativo ASP.NET Core:
Middleware do CORS – políticas CORS se aplicam globalmente para o aplicativo por meio do middleware.
O CORS no MVC – políticas aplicar CORS por controlador ou por ação. Middleware CORS não é usada.
Habilitar o CORS com Middleware CORS
Middleware CORS manipula solicitações entre origens para o aplicativo. Para habilitar o CORS Middleware no
pipeline de processamento de solicitação, chame o UseCors método de extensão no Startup.Configure .
Middleware CORS devem preceder quaisquer pontos de extremidade definidos em seu aplicativo onde você
deseja dar suporte a solicitações entre origens (por exemplo, antes de chamar UseMvc de Middleware de
páginas do Razor/MVC ).
Um política entre origens pode ser especificado ao adicionar o Middleware CORS usando o CorsPolicyBuilder
classe. Há duas abordagens para definir uma política CORS:
Chamar UseCors com um lambda:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
O lambda utiliza um CorsPolicyBuilder objeto. Opções de configuração, tais como WithOrigins , são
descritas posteriormente neste tópico. No exemplo anterior, a política permite que solicitações entre
origens de https://example.com e sem outras origens.
A URL deve ser especificada sem uma barra à direita ( / ). Se a URL termina com / , a comparação
retorna false e nenhum cabeçalho é retornado.
CorsPolicyBuilder tem uma API fluente, portanto, é possível encadear chamadas de método:
app.UseCors(builder =>
builder.WithOrigins("http://example.com")
.AllowAnyHeader()
);
Definir uma ou mais políticas CORS e selecione a política por nome em tempo de execução. O exemplo a
seguir adiciona uma política CORS definida pelo usuário chamada AllowSpecificOrigin. Para selecionar a
política, passe o nome para UseCors :
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
[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 a [EnableCors] atributo à classe do
controlador. Especifique o nome da política.
[Route("api/[controller]")]
[EnableCors("AllowSpecificOrigin")]
public class ValuesController : ControllerBase
A ordem de precedência é:
1. ação
2. controlador
Desabilitar o 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");
});
AllowAnyOrigin – Permite que as solicitações CORS de todas as origens com qualquer esquema ( http
ou https ).
options.AddPolicy("AllowAllOrigins",
builder =>
{
builder.AllowAnyOrigin();
});
Considere cuidadosamente antes de permitir que solicitações de qualquer origem. Permitir solicitações de
qualquer origem significa que de qualquer site pode fazer solicitações entre origens ao seu aplicativo.
NOTE
Especificando AllowAnyOrigin e AllowCredentials é uma configuração insegura e podem resultar em
falsificação de solicitação entre sites. O serviço CORS retorna uma resposta inválida do CORS, quando um
aplicativo é configurado com os dois métodos.
NOTE
Especificando AllowAnyOrigin e AllowCredentials é uma configuração insegura e podem resultar em
falsificação de solicitação entre sites. Considere a especificação de uma lista exata de origens se o cliente deve
autorizar a mesmo para acessar recursos do servidor.
options.AddPolicy("AllowSubdomain",
builder =>
{
builder.SetIsOriginAllowedToAllowWildcardSubdomains();
});
options.AddPolicy("AllowAllMethods",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyMethod();
});
options.AddPolicy("AllowHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithHeaders(HeaderNames.ContentType, "x-custom-header");
});
Middleware CORS recusar uma solicitação de simulação com o seguinte cabeçalho de solicitação, pois
Content-Language ( HeaderNames.ContentLanguage) não está listado no WithHeaders :
O aplicativo retorna um 200 Okey resposta, mas não envia de volta os cabeçalhos de CORS. Portanto, o
navegador não tente a solicitação entre origens.
Middleware CORS sempre permite que os cabeçalhos de quatro no Access-Control-Request-Headers a ser
enviado, independentemente dos valores configurados no CorsPolicy.Headers. Esta lista de cabeçalhos inclui:
Accept
Accept-Language
Content-Language
Origin
Middleware CORS responde com êxito a uma solicitação de simulação com o seguinte cabeçalho de solicitação
porque Content-Language é sempre na lista de permissões:
A especificação CORS chama esses cabeçalhos cabeçalhos de resposta simples. Para disponibilizar outros
cabeçalhos para o aplicativo, chame WithExposedHeaders:
options.AddPolicy("ExposeResponseHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithExposedHeaders("x-custom-header");
});
No jQuery:
$.ajax({
type: 'get',
url: 'https://www.example.com/home',
xhrFields: {
withCredentials: true
}
Além disso, o servidor deve permitir que as credenciais. Para permitir credenciais entre origens, chamar
AllowCredentials:
options.AddPolicy("AllowCredentials",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowCredentials();
});
A regra em cabeçalhos de solicitação definido para a solicitação do cliente se aplica aos cabeçalhos que o
aplicativo define chamando setRequestHeader sobre o XMLHttpRequest objeto. A especificação CORS chama
esses cabeçalhos criar cabeçalhos de solicitação. A regra não se aplica aos cabeçalhos, o navegador pode definir,
tais como User-Agent , Host , ou Content-Length .
Este é um exemplo de uma solicitação de simulação:
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 define a solicitação
real. Conforme mencionado anteriormente, isso não inclui os cabeçalhos que define o navegador, tais como
User-Agent .
Uma solicitação de simulação de CORS pode incluir um Access-Control-Request-Headers cabeçalho, que indica
ao servidor, os cabeçalhos que são enviados com a solicitação real.
Para permitir que os cabeçalhos específicos, chame WithHeaders:
options.AddPolicy("AllowHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithHeaders(HeaderNames.ContentType, "x-custom-header");
});
Navegadores não estão totalmente consistentes em como eles definir Access-Control-Request-Headers . Se você
definir os cabeçalhos para algo diferente de "*" (ou use AllowAnyHeader), você deve incluir pelo menos
Accept , Content-Type , e Origin , além de quaisquer cabeçalhos personalizados que você deseja dar suporte.
Este é um exemplo de resposta à solicitação de simulação (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: https://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
options.AddPolicy("SetPreflightExpiration",
builder =>
{
builder.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});
Se o servidor permite que a solicitação, ele define o Access-Control-Allow-Origin cabeçalho na resposta. O valor
desse cabeçalho também corresponde a Origin cabeçalho da solicitação ou é 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: https://myclient.azurewebsites.net
Date: Wed, 20 May 2015 06:27:30 GMT
Content-Length: 12
Test message
Recursos adicionais
Cross-Origin Resource Sharing (CORS )
Compartilhar cookies entre aplicativos com o
ASP.NET e ASP.NET Core
30/10/2018 • 11 minutes to read • Edit Online
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 exemplo, 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.
Ao hospedar aplicativos que compartilham cookies entre subdomínios, especifique um domínio comum na
Cookie.Domain propriedade. Compartilhar cookies entre aplicativos no contoso.com , como
first_subdomain.contoso.com e second_subdomain.contoso.com , especifique o Cookie.Domain como .contoso.com :
options.Cookie.Domain = ".contoso.com";
var protectionProvider =
DataProtectionProvider.Create(
new DirectoryInfo(@"PATH_TO_KEY_RING_FOLDER"));
options.Cookies.ApplicationCookie.DataProtectionProvider =
protectionProvider;
options.Cookies.ApplicationCookie.TicketDataFormat =
new TicketDataFormat(protectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
"Cookies",
"v2"));
});
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 exemplo, 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.
Ao hospedar aplicativos que compartilham cookies entre subdomínios, especifique um domínio comum na
Cookie.Domain propriedade. Compartilhar cookies entre aplicativos no contoso.com , como
first_subdomain.contoso.com e second_subdomain.contoso.com , especifique o Cookie.Domain como .contoso.com :
options.Cookie.Domain = ".contoso.com";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
DataProtectionProvider =
DataProtectionProvider.Create(
new DirectoryInfo(@"PATH_TO_KEY_RING_FOLDER"))
});
services.AddDataProtection()
.ProtectKeysWithCertificate("thumbprint");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
DataProtectionProvider = DataProtectionProvider.Create(
new DirectoryInfo(@"PATH_TO_KEY_RING"),
configure =>
{
configure.ProtectKeysWithCertificate("thumbprint");
})
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Identity.Application",
CookieName = ".AspNet.SharedCookie",
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator
.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) =>
user.GenerateUserIdentityAsync(manager))
},
TicketDataFormat = new AspNetTicketDataFormat(
new DataProtectorShim(
DataProtectionProvider.Create(GetKeyRingDirInfo(),
(builder) => { builder.SetApplicationName("SharedCookieApp"); })
.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
"Identity.Application",
"v2"))),
CookieManager = new ChunkingCookieManager()
});
Models/IdentityModels.cs:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in
CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, "Identity.Application");
// Add custom user claims here
return userIdentity;
}
Recursos adicionais
Hospedar o ASP.NET Core em um web farm
Lista segura IP do cliente para o ASP.NET Core
30/10/2018 • 5 minutes to read • Edit Online
A lista segura
A lista é configurada na appSettings. JSON arquivo. Ele é uma lista delimitada por ponto e vírgula e pode conter
endereços IPv4 e IPv6.
{
"AdminSafeList": "127.0.0.1;192.168.1.5;::1",
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
Middleware
O Configure método adiciona o middleware e passa a cadeia de caracteres de lista segura para ele em um
parâmetro de construtor.
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddNLog();
app.UseStaticFiles();
app.UseMiddleware<AdminSafeListMiddleware>(
Configuration["AdminSafeList"]);
app.UseMvc();
}
O middleware analisa a cadeia de caracteres em uma matriz e procura o endereço IP remoto na matriz. Se o
endereço IP remoto não for encontrado, o middleware retornará HTTP 401 proibido. Esse processo de validação é
ignorado para solicitações HTTP Get.
public class AdminSafeListMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<AdminSafeListMiddleware> _logger;
private readonly string _adminSafeList;
public AdminSafeListMiddleware(
RequestDelegate next,
ILogger<AdminSafeListMiddleware> logger,
string adminSafeList)
{
_adminSafeList = adminSafeList;
_next = next;
_logger = logger;
}
string[] ip = _adminSafeList.Split(';');
if(badIp)
{
_logger.LogInformation(
$"Forbidden Request from Remote IP address: {remoteIp}");
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
}
await _next.Invoke(context);
}
}
Filtro de ação
Se você quiser uma lista segura somente para controladores específicos ou métodos de ação, use um filtro de ação.
Veja um exemplo:
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace ClientIpAspNetCore.Filters
{
public class ClientIdCheckFilter : ActionFilterAttribute
{
private readonly ILogger _logger;
private readonly string _safelist;
public ClientIdCheckFilter
(ILoggerFactory loggerFactory, IConfiguration configuration)
{
_logger = loggerFactory.CreateLogger("ClientIdCheckFilter");
_safelist = configuration["AdminSafeList"];
}
string[] ip = _safelist.Split(';');
if (badIp)
{
_logger.LogInformation(
$"Forbidden Request from Remote IP address: {remoteIp}");
context.Result = new StatusCodeResult(401);
return;
}
base.OnActionExecuting(context);
}
}
}
services.AddMvc(options =>
{
options.Filters.Add
(new ClientIdCheckPageFilter
(_loggerFactory, Configuration));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
[ServiceFilter(typeof(ClientIdCheckFilter))]
[HttpGet]
public IEnumerable<string> Get()
No aplicativo de exemplo, o filtro é aplicado para o Get método. Portanto, quando você testar o aplicativo,
enviando um Get solicitação de API, o atributo é validar o endereço IP do cliente. Quando você testa chamando a
API com qualquer outro método HTTP, o middleware é validar o IP do cliente.
namespace ClientIpAspNetCore
{
public class ClientIdCheckPageFilter : IPageFilter
{
private readonly ILogger _logger;
private readonly string _safelist;
public ClientIdCheckPageFilter
(ILoggerFactory loggerFactory, IConfiguration configuration)
{
_logger = loggerFactory.CreateLogger("ClientIdCheckPageFilter");
_safelist = configuration["AdminSafeList"];
}
string[] ip = _safelist.Split(';');
if (badIp)
{
_logger.LogInformation(
$"Forbidden Request from Remote IP address: {remoteIp}");
context.Result = new StatusCodeResult(401);
return;
}
}
services.AddMvc(options =>
{
options.Filters.Add
(new ClientIdCheckPageFilter
(_loggerFactory, Configuration));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Quando você executa o aplicativo e solicita uma página Razor, o filtro de páginas do Razor é validar o IP do cliente.
Próximas etapas
Saiba mais sobre o Middleware do ASP.NET Core.
Práticas recomendadas de desempenho do ASP.NET
Core
08/01/2019 • 14 minutes to read • Edit Online
Compactar respostas
Reduzindo o tamanho da resposta geralmente aumenta a capacidade de resposta de um aplicativo, muitas vezes
drasticamente. É uma maneira de reduzir os tamanhos do conteúdo compactar respostas do aplicativo. Para obter
mais informações, consulte compactação de resposta.
Minimizar as exceções
As exceções devem ser raras. Lançar e capturar exceções são lento em relação a outros padrões de fluxo de código.
Por causa disso, as exceções não devem ser usadas para controlar o fluxo normal do programa.
Recomendações:
Não uso gerar ou capturar exceções como um meio de fluxo normal do programa, especialmente em caminhos
de código a quente.
Fazer incluir lógica no aplicativo para detectar e lidar com condições que poderiam causar uma exceção.
Fazer lançar ou capturar exceções para condições incomuns ou inesperadas.
Ferramentas de diagnóstico de aplicativo (como o Application Insights) podem ajudar a identificar exceções
comuns em um aplicativo que podem afetar o desempenho.
Cache de resposta no ASP.NET Core
08/01/2019 • 16 minutes to read • Edit Online
NOTE
Cache de resposta nas páginas do Razor está disponível no ASP.NET Core 2.1 ou posterior.
DIRETIVA AÇÃO
max-age O cliente não aceitará uma resposta cuja idade é maior que o
número especificado de segundos. Exemplos: max-age=60
(60 segundos), max-age=2592000 (1 mês)
DIRETIVA AÇÃO
Outros cabeçalhos de cache que desempenham uma função em 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 de 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 a configuração de cabeçalhos apropriados
no cache de resposta.
WARNING
Desabilite o cache para o conteúdo que contém informações para clientes autenticados. Armazenamento em cache deve
ser habilitado apenas para conteúdo que não são alteradas com base na identidade do usuário ou se um usuário está
conectado.
VaryByQueryKeys a resposta armazenada de varia de acordo com os valores de determinada lista de chaves de
consulta. Quando um valor único de * é fornecido, o middleware varia as respostas de todos os parâmetros de
cadeia de caracteres de consulta de solicitação. VaryByQueryKeys exige o ASP.NET Core 1.1 ou posterior.
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 há um cabeçalho HTTP correspondente para o
VaryByQueryKeys propriedade. A propriedade é um recurso HTTP tratado pelo Middleware de cache de 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 com uma solicitação anterior. Por exemplo, considere a sequência de
solicitações e os 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, como a cadeia de caracteres de consulta corresponde a solicitação anterior. A
terceira solicitação não está no cache do 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 (via IFilterFactory ) uma ResponseCacheFilter. O
ResponseCacheFilter realiza o trabalho de atualização de cabeçalhos HTTP apropriados e 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 ResponseCacheAttribute .
Atualiza a resposta de armazenamento em cache o recurso HTTP se VaryByQueryKeys está definido.
Variar
Esse cabeçalho só será gravado quando o VaryByHeader propriedade 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 F12 borda de saída na 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 está 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:
Cache-Control: no-store,no-cache
Pragma: no-cache
Local e a duração
Para habilitar o cache, Duration deve ser definida como um valor positivo e Location deve ser Any (o padrão)
ou Client . Nesse caso, o Cache-Control cabeçalho é definido como o valor do local seguido o max-age da
resposta.
NOTE
Location da opções dos Any e Client traduzir Cache-Control valores de cabeçalho da public e private ,
respectivamente. Conforme observado anteriormente, definindo Location à None define ambas Cache-Control e
Pragma cabeçalhos para no-cache .
Veja abaixo um exemplo que mostra os cabeçalhos é produzido pela configuração Duration e deixar o padrão
Location valor:
[ResponseCache(Duration = 60)]
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
[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 de muitos atributos de ação do controlador, perfis de cache
podem ser configuradas como opções ao configurar o MVC em de ConfigureServices método na Startup .
Valores encontrados em um perfil de cache referenciada são usados como padrões pelo ResponseCache de
atributos e são substituídas por qualquer propriedade especificada no atributo.
Como configurar um perfil de cache:
[ResponseCache(Duration = 30)]
public class HomeController : Controller
{
[ResponseCache(CacheProfileName = "Default")]
public IActionResult Index()
{
return View();
}
[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 no nível de classe de atributos.
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
Armazena as respostas em Caches
Cache-Control
Memória de cache no ASP.NET Core
O cache no ASP.NET Core distribuído
Detectar alterações com tokens de alteração no ASP.NET Core
Middleware no ASP.NET Core de cache de resposta
Auxiliar de Marca de Cache no ASP.NET Core MVC
Auxiliar de Marca de Cache Distribuído no ASP.NET Core
Memória de cache no ASP.NET Core
12/02/2019 • 15 minutes to read • Edit Online
System.Runtime.Caching/MemoryCache
System.Runtime.Caching/MemoryCache (Pacote do NuGet) pode ser usado com:
.NET standard 2.0 ou posterior.
Qualquer implementação do .NET que tem como alvo o .NET Standard 2.0 ou posterior. Por exemplo,
ASP.NET Core 2.0 ou posterior.
.NET framework 4.5 ou posterior.
Extensions / IMemoryCache (descrita neste tópico) é mais recomendada System.Runtime.Caching / MemoryCache
porque ele é melhor integrado ao ASP.NET Core. Por exemplo, IMemoryCache funciona nativamente com o
ASP.NET Core injeção de dependência.
Use System.Runtime.Caching / MemoryCache como uma ponte de compatibilidade ao portar código do ASP.NET
4.x ASP.NET Core.
Diretrizes de cache
Código deveria ter sempre uma opção de fallback para buscar dados e não dependem de um valor em
cache que está sendo disponível.
O cache usa um recurso escasso, de memória. Limitar o crescimento de cache:
Fazer não usar entrada externo como chaves de cache.
Use as expirações para limitar o crescimento de cache.
Usar SetSize, tamanho e SizeLimit para limitar o tamanho do cache
Usando IMemoryCache
O cache na memória é um service que é referenciado em seu aplicativo usando injeção de dependência. Chame
AddMemoryCache em ConfigureServices :
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
@model DateTime?
<div>
<h2>Actions</h2>
<ul>
<li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
<li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAsync">GetOrCreateAsync</a></li>
<li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
</ul>
</div>
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.
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 torne muito desatualizado quando a expiração deslizante continuamente é
renovada.
Define um tempo de expiração deslizante. Solicitações que acessam esse item em cache 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.
return RedirectToAction("GetCallbackEntry");
}
// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache
{
public MemoryCache Cache { get; set; }
public MyMemoryCache()
{
Cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 1024
});
}
}
SizeLimit não tem unidades. As entradas em cache devem especificar o tamanho em qualquer unidade que
consideram mais apropriados se o tamanho da memória de cache tiver sido definido. Todos os usuários de
uma instância de cache devem usar a mesma unidade de sistema. Uma entrada não será armazenada, se a
soma dos tamanhos da entrada armazenada em cache excede o valor especificado pelo SizeLimit . Se nenhum
limite de tamanho do cache for definido, o tamanho do cache definida na entrada será ignorado.
O código a seguir registra MyMemoryCache com o injeção de dependência contêiner.
services.AddSingleton<MyMemoryCache>();
}
MyMemoryCache é criado como um cache de memória independentes para os componentes que reconhecem
esse cache de tamanho limitado e saber como definir o tamanho do cache de entrada adequadamente.
O seguinte código usa MyMemoryCache :
public class AboutModel : PageModel
{
private MemoryCache _cache;
public static readonly string MyKey = "_MyKey";
[TempData]
public string DateTime_Now { get; set; }
DateTime_Now = cacheEntry;
return RedirectToPage("./Index");
}
}
O tamanho da entrada de cache pode ser definido tamanho ou o SetSize método de extensão:
public IActionResult OnGet()
{
if (!_cache.TryGetValue(MyKey, out string cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now.TimeOfDay.ToString();
DateTime_Now = cacheEntry;
return RedirectToPage("./Index");
}
Dependências de cache
O exemplo a seguir mostra como expirar uma entrada de cache, se uma entrada dependente expira. 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 localizar o valor da chave em cache vazio porque o retorno de chamada
não foi concluído.
Isso pode resultar em vários threads repopulação o item em cache.
Quando uma entrada de cache é usada para criar outro, o filho copia a entrada de pai tokens de
expiração e as configurações de expiração com base no tempo. O filho não está expirada pela remoção
manual ou a atualização da entrada pai.
Use PostEvictionCallbacks para definir os retornos de chamada que serão acionados depois que a
entrada de cache é removida do cache.
Recursos adicionais
O cache no ASP.NET Core distribuído
Detectar alterações com tokens de alteração no ASP.NET Core
Cache de resposta no ASP.NET Core
Middleware no ASP.NET Core de cache de resposta
Auxiliar de Marca de Cache no ASP.NET Core MVC
Auxiliar de Marca de Cache Distribuído no ASP.NET Core
O cache no ASP.NET Core distribuído
31/10/2018 • 13 minutes to read • Edit Online
Pré-requisitos
Para usar um SQL Server distributed cache, a referência a metapacote do Microsoft ou adicionar uma
referência de pacote para o Microsoft.Extensions.Caching.SqlServer pacote.
Para usar um Redis distributed cache, a referência a metapacote do Microsoft e adicione uma referência de
pacote para o Microsoft.Extensions.Caching.Redis pacote. O pacote do Redis não está incluído no
Microsoft.AspNetCore.App empacotar, portanto, você deve referenciar o pacote de Redis separadamente no
seu arquivo de projeto.
Para usar um SQL Server distributed cache, a referência a metapacote Microsoft.AspNetCore.All ou
adicionar uma referência de pacote para o Microsoft.Extensions.Caching.SqlServer pacote.
Para usar um Redis distributed cache, a referência a metapacote Microsoft.AspNetCore.All ou adicionar uma
referência de pacote para o Microsoft.Extensions.Caching.Redis pacote. O pacote de Redis está incluído no
Microsoft.AspNetCore.All empacotar, para que você não precisa fazer referência ao pacote Redis
separadamente no seu arquivo de projeto.
Para usar um SQL Server distributed cache, adicione uma referência de pacote para o
Microsoft.Extensions.Caching.SqlServer pacote.
Para usar um Redis cache distribuído, adicione uma referência de pacote para o
Microsoft.Extensions.Caching.Redis pacote.
Interface IDistributedCache
O IDistributedCache interface fornece os seguintes métodos para manipular itens na implementação de
cache distribuído:
Get, GetAsync – Aceita uma chave de cadeia de caracteres e recupera um item em cache como um
byte[] matriz se encontrada no cache.
Set, SetAsync – Adiciona um item (como byte[] matriz) para o cache usando uma chave de cadeia de
caracteres.
Refresh, RefreshAsync – Atualiza um item no cache com base em sua chave, redefinindo seu tempo limite
de expiração deslizante (se houver).
Remove, RemoveAsync – Remove um item de cache com base em sua chave de cadeia de caracteres.
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.Caching.SqlConfig.Tools"
Version="2.0.2" />
</ItemGroup>
Crie uma tabela no SQL Server executando o sql-cache create comando. Fornecer a instância do SQL
Server ( Data Source ), banco de dados ( Initial Catalog ), esquema (por exemplo, dbo ) e o nome da tabela
(por exemplo, TestCache ):
NOTE
Um aplicativo deve manipular valores de cache usando uma instância de IDistributedCache, e não um SqlServerCache.
services.AddDistributedRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "SampleInstance";
});
O aplicativo de exemplo injeta IDistributedCache para o IndexModel para uso pela página de índice.
Cada vez que a página de índice é carregada, o cache é verificado para o tempo em cache em OnGetAsync .
Se ainda não tiver expirado o tempo em cache, a hora é exibida. Se 20 segundos decorridos desde a última
vez em que o tempo em cache foi acessado (a última vez que esta página foi carregada), a página exibe
armazenado em cache tempo expirado.
Atualizar imediatamente o tempo em cache para a hora atual, selecionando o redefinição de tempo em
cache botão. Os gatilhos de botão a OnPostResetCachedTime método do manipulador.
public class IndexModel : PageModel
{
private readonly IDistributedCache _cache;
if (encodedCachedTimeUTC != null)
{
CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
}
}
return RedirectToPage();
}
}
NOTE
Não é necessário usar um tempo de vida Singleton ou com escopo para IDistributedCache instâncias (pelo menos
para as implementações internas).
Você também pode criar uma IDistributedCache da instância onde você pode precisar de uma em vez de usar a
DI, mas a criação de uma instância no código pode tornar seu código mais difícil de testar e viola o princípio de
dependências explícitas.
Recomendações
Ao decidir qual implementação de IDistributedCache é melhor para seu aplicativo, considere o seguinte:
Infraestrutura existente
Requisitos de desempenho
Custo
Experiência da equipe
Soluções de cache normalmente se baseiam em armazenamento na memória para fornecer recuperação
rápida de dados armazenados em cache, mas a memória é um recurso limitado e caros expandir. Loja
apenas dados usados normalmente em um cache.
Em geral, um cache Redis fornece maior taxa de transferência e latência mais baixa que o cache de SQL
Server. No entanto, é geralmente necessário para determinar as características de desempenho de
estratégias de cache de benchmark.
Quando o SQL Server é usado como um armazenamento de backup de cache distribuído, usar o mesmo
banco de dados para o cache e armazenamento de dados comum do aplicativo e recuperação pode afetar
negativamente o desempenho de ambos. É recomendável usar uma instância dedicada do SQL Server para
o cache distribuído do repositório de backup.
Recursos adicionais
Redis Cache no Azure
Banco de dados SQL no Azure
Provedor de IDistributedCache para NCache nos Farms da Web do ASP.NET Core (NCache no GitHub)
Memória de cache no ASP.NET Core
Detectar alterações com tokens de alteração no ASP.NET Core
Cache de resposta no ASP.NET Core
Middleware no ASP.NET Core de cache de resposta
Auxiliar de Marca de Cache no ASP.NET Core MVC
Auxiliar de Marca de Cache Distribuído no ASP.NET Core
Hospedar o ASP.NET Core em um web farm
Middleware no ASP.NET Core de cache de resposta
30/10/2018 • 13 minutes to read • Edit Online
Pacote
Referência a metapacote do Microsoft ou adicionar uma referência de pacote para o
Microsoft.AspNetCore.ResponseCaching pacote.
Referência a metapacote Microsoft.AspNetCore.All ou adicionar uma referência de pacote para o
Microsoft.AspNetCore.ResponseCaching pacote.
Adicionar uma referência de pacote para o Microsoft.AspNetCore.ResponseCaching pacote.
Configuração
No Startup.ConfigureServices , adicione o middleware para a coleção de serviço.
services.AddResponseCaching();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Configurar o aplicativo para usar o middleware com o UseResponseCaching método de extensão, que adiciona o
middleware ao pipeline de processamento da solicitação. O aplicativo de exemplo adiciona uma Cache-Control
cabeçalho à resposta que armazena em cache respostas armazenáveis em cache por até 10 segundos. O
exemplo envia uma Vary cabeçalho para configurar o middleware para servir a uma resposta em cache
somente se o Accept-Encoding cabeçalho das solicitações subsequentes corresponde da solicitação original. No
exemplo de código a seguir, CacheControlHeaderValue e HeaderNames exigir uma using instrução para o
Microsoft.Net.Http.Headers namespace.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseResponseCaching();
await next();
});
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.
WARNING
Respostas que contêm o conteúdo para clientes autenticados devem ser marcadas como não armazenável em cache para
impedir que o middleware de armazenamento e que atende a essas respostas. Ver condições para armazenar em cache
para obter detalhes sobre como o middleware determina se uma resposta é armazenável em cache.
Opções
O middleware oferece três opções para controlar o cache de resposta.
OPÇÃO DESCRIÇÃO
services.AddResponseCaching(options =>
{
options.UseCaseSensitivePaths = true;
options.MaximumBodySize = 1024;
});
VaryByQueryKeys
Ao usar controladores de API da Web do MVC ou modelos de página de páginas do 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 que estritamente requer o middleware é VaryByQueryKeys , que
não corresponde a um cabeçalho HTTP real. Para obter mais informações, consulte do atributo
ResponseCache.
Quando não estiver usando o ResponseCache atributo, cache de resposta pode ser variado com o
VaryByQueryKeys recurso. Use o ResponseCachingFeature diretamente do IFeatureCollection da HttpContext :
Usando um único valor igual a * em VaryByQueryKeys o cache de varia de acordo com todos os parâmetros
de consulta de solicitação.
CABEÇALHO DETALHES
Solução de problemas
Se o comportamento de cache não é conforme o esperado, confirme que as respostas sejam armazenável em
cache e capaz de sendo servido do cache. Examine os cabeçalhos de entrada da solicitação e cabeçalhos de
saída da resposta. Habilitar registro em log para ajudar na depuração.
Ao 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 a
Cache-Control cabeçalho no-cache ou max-age=0 durante a atualização de 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 deverá estar presente.
Cache-Control parâmetros do cabeçalho devem ser válidos, e a resposta deve ser marcada public e não
marcada private .
O Pragma: no-cache cabeçalho não deverá estar presente se o 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 deverá estar presente.
Vary parâmetros do 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 obsoleta 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 armazenáveis em cache de acordo com o RFC 7234 especificações. Por exemplo, o
no-store diretiva não deve existir nos campos de cabeçalho de solicitação ou resposta. Ver seção 3:
armazenar respostas em Caches dos RFC 7234 para obter detalhes.
NOTE
O sistema Antifalsificação para gerar tokens de seguras para evitar a falsificação de solicitação entre sites (CSRF) attacks
conjuntos a 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 tokens antifalsificação para elementos de formulário HTML,
consulte configuração antifalsificação do ASP.NET Core.
Recursos adicionais
Inicialização de aplicativo no ASP.NET Core
Middleware do ASP.NET Core
Memória de cache no ASP.NET Core
O cache no ASP.NET Core distribuído
Detectar alterações com tokens de alteração no ASP.NET Core
Cache de resposta no ASP.NET Core
Auxiliar de Marca de Cache no ASP.NET Core MVC
Auxiliar de Marca de Cache Distribuído no ASP.NET Core
Compactação de resposta no ASP.NET Core
08/01/2019 • 23 minutes to read • Edit Online
Compactação de resposta
Geralmente, nenhuma resposta compactada nativamente não pode se beneficiar da compactação de resposta. As
respostas não nativamente compactadas normalmente incluem: CSS, JavaScript, HTML, XML e JSON. Você não
deve compactar os ativos nativamente compactados, como arquivos PNG. Se você tentar compactar ainda mais
uma resposta compactada nativamente, qualquer redução adicional pequena em tempo de tamanho e a
transmissão será provavelmente ser superada pelo tempo levado para processar a compactação. Não compacte
arquivos menores do 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 descompactado.
Quando um cliente pode processar o 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 o conteúdo compactado, ele
deve incluir informações no Content-Encoding cabeçalho em como a resposta compactada é codificada. Conteúdo
designações de codifica com suporte pelo middleware são mostradas na tabela a seguir.
Para obter mais informações, consulte o IANA lista oficial de conteúdo de codificação.
O middleware permite que você adicione provedores de compactação adicional para custom 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 ponderação. Para obter mais informações, consulte RFC 7231: Codificação aceita.
Algoritmos de compactação estão sujeitos a uma compensação entre a velocidade de compactação e a eficiência
da compactação. Eficácia neste contexto refere-se ao tamanho da saída após a compactação. O menor tamanho é
obtido com a maioria ideal compactação.
Cabeçalhos envolvidas na solicitação, enviar, armazenamento em cache e receber conteúdo compactado são
descritas na tabela a seguir.
CABEÇALHO FUNÇÃO
CABEÇALHO FUNÇÃO
Explore os recursos do Middleware de compactação de resposta com o aplicativo de exemplo. O exemplo ilustra:
A compactação de respostas do aplicativo usando o Gzip e provedores de compactação personalizado.
Como adicionar um tipo de MIME para a lista padrão de tipos MIME para compactação.
Pacote
Para incluir o middleware em um projeto, adicione uma referência para o metapacote do Microsoft, que inclui o
Microsoft.AspNetCore.ResponseCompression pacote.
Para incluir o middleware em um projeto, adicione uma referência para o metapacote Microsoft.AspNetCore.All,
que inclui o Microsoft.AspNetCore.ResponseCompression pacote.
Para incluir o middleware em um projeto, adicione uma referência para o
Microsoft.AspNetCore.ResponseCompression pacote.
Configuração
O código a seguir mostra como habilitar o Middleware de compactação de resposta para provedores de
compactação e tipos MIME padrão (Brotli e Gzip):
O código a seguir mostra como habilitar o Middleware de compactação de resposta para tipos MIME padrão e o
provedor de compactação Gzip:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression();
}
Notas:
app.UseResponseCompression deve ser chamado antes de app.UseMvc .
Usar uma ferramenta como Fiddler, Firebug, ou Postman para definir o Accept-Encoding cabeçalho de
solicitação e estudar os cabeçalhos de resposta, o tamanho e o corpo.
Enviar uma solicitação para o aplicativo de exemplo sem o Accept-Encoding cabeçalho e observe que a resposta é
descompactada. O Content-Encoding e Vary cabeçalhos não estiverem presentes na resposta.
Enviar uma solicitação para o aplicativo de exemplo com o Accept-Encoding: br cabeçalho (a compactação Brotli)
e observe que a resposta é compactada. O Content-Encoding e Vary cabeçalhos 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
Provedor de compactação Brotli
Use o BrotliCompressionProvider para compactar respostas com o formato de dados compactados Brotli.
Se nenhum provedor de compactação é adicionados explicitamente a CompressionProviderCollection:
O provedor de compactação Brotli é adicionado por padrão para a matriz de provedores de compactação
juntamente com o provedor de compactação Gzip.
Padrões de compactação para a compactação Brotli quando o formato de dados compactados Brotli é
suportado pelo cliente. Se Brotli não é suportada pelo cliente, a compactação padrão Gzip quando o cliente
oferece suporte à compactação Gzip.
O provedor de compactação Brotoli devem ser adicionado ao quaisquer provedores de compactação são
adicionados explicitamente:
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<CustomCompressionProvider>();
options.MimeTypes =
ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "image/svg+xml" });
});
}
services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
}
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
}
Provedores personalizados
Criar implementações de compactação personalizado com ICompressionProvider. O EncodingName representa
o conteúdo de codificação que este ICompressionProvider produz. O middleware usa essas informações para
escolher o provedor 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 de compactação personalizado e retorna a resposta com um
Content-Encoding: mycustomcompression cabeçalho. O cliente deve ser capaz de descompactar a codificação
personalizada para que uma implementação de compactação personalizado trabalhar.
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 de resposta (não mostrado) não será compactado pelo exemplo. Não existe uma implementação da
compactação no CustomCompressionProvider classe do exemplo. No entanto, o exemplo mostra onde você poderia
implementar tal um algoritmo de compactação.
tipos MIME
O middleware Especifica um conjunto de tipos MIME para a compactação padrão:
application/javascript
application/json
application/xml
text/css
text/html
text/json
text/plain
text/xml
Substituir ou acrescentar os tipos MIME com as opções de Middleware de compactação de resposta. Observe
que curinga MIME tipos, como text/* não são suportados. O aplicativo de exemplo adiciona um tipo MIME
image/svg+xml e compacta e serve o ASP.NET Core a imagem da faixa ( banner.svg ).
Solução de problemas
Use uma ferramenta como Fiddler, Firebug, ou Postman, que permitem que você defina o Accept-Encoding
cabeçalho de solicitação e estudar os cabeçalhos de resposta, o tamanho e o corpo. Por padrão, o Middleware de
compactação de resposta compacta as respostas que atendem às seguintes condições:
O Accept-Encoding cabeçalho está presente com um valor de br , gzip , * , ou codificação personalizada que
corresponde a um provedor de compactação personalizado 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 protocolo inseguro (http), a menos que o protocolo seguro (https) é configurado nas
opções de Middleware de compactação de resposta. Observe o perigo descritos acima ao habilitar a
compactação de conteúdo segura.
O Accept-Encoding cabeçalho está presente com um valor de gzip , * , ou codificação personalizada que
corresponde a um provedor de compactação personalizado 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 protocolo inseguro (http), a menos que o protocolo seguro (https) é configurado nas
opções de Middleware de compactação de resposta. Observe o perigo descritos acima ao habilitar a
compactação de conteúdo segura.
Recursos adicionais
Inicialização de aplicativo no ASP.NET Core
Middleware do ASP.NET Core
Rede de desenvolvedor do Mozilla: Codificação aceita
RFC 7231 seção 3.1.2.1: Conteúdo Codings
RFC 7230 seção 4.2.3: A codificação gzip
Versão de especificação de formato de arquivo GZIP 4.3
Ferramentas de diagnóstico de desempenho
08/01/2019 • 5 minutes to read • Edit Online
Informações do aplicativo
Application Insights fornece dados de desempenho detalhados para seu aplicativo. Application Insights coleta
automaticamente dados em taxas de resposta, taxas de falha, os tempos de resposta de dependência e muito mais.
Application Insights dá suporte ao registro em log eventos personalizados e métricas específicas para seu
aplicativo.
O Azure Application Insights fornece várias maneiras de fornecer informações nos aplicativos monitorados:
Mapa do aplicativo – ajuda a identificar gargalos de desempenho ou falha pontos em todos os componentes
de aplicativos distribuídos.
Folha métricas no portal do Application Insights mostra valores de medida e contagens de eventos.
Folha de desempenho no portal do Application Insights:
Mostra detalhes de desempenho para operações diferentes no aplicativo monitorado.
Permite que o drill down em uma única operação para verificar todas as partes/dependências que
contribuem para um longo tempo.
Profiler pode ser invocado a partir daqui para coletar rastreamentos de desempenho sob demanda.
De do Azure Application Insights Profiler permite regular e sob demanda de criação de perfil de aplicativos
.NET. Mostra portal do Azure capturado rastreamentos de desempenho com as pilhas de chamadas e
caminhos de acesso. Os arquivos de rastreamento também podem ser baixados para análise mais profunda
com o PerfView.
Application Insights podem ser usados em ambientes de uma variedade:
Otimizado para funcionar no Azure.
Funciona em produção, desenvolvimento e preparo.
Funciona localmente a partir Visual Studio ou em outros ambientes de hospedagem.
Para obter mais informações, veja Application Insights para ASP.NET Core.
PerfView
O PerfView é uma ferramenta de análise de desempenho criada pela equipe do .NET especificamente para
diagnosticar problemas de desempenho do .NET. O PerfView permite que a análise do CPU uso, memória e GC
comportamento, eventos de desempenho e a hora do relógio.
Você pode aprender mais sobre como começar com e o PerfView tutoriais em vídeo PerfView ou ao ler o guia do
usuário disponível na ferramenta ou no GitHub.
PerfCollect
Enquanto o PerfView é uma ferramenta de análise de desempenho úteis para cenários do .NET, ele só é executado
no Windows para que você não pode usá-lo para coletar rastreamentos de aplicativos do ASP.NET Core em
execução em ambientes Linux.
PerfCollect é um script bash que usa as ferramentas de criação de perfil de Linux nativo (Perf e LTTng) para coletar
rastreamentos no Linux que pode ser analisado por PerfView. PerfCollect é útil quando problemas de desempenho
aparecem em ambientes Linux em que o PerfView não pode ser usado diretamente. Em vez disso, PerfCollect pode
coletar rastreamentos de aplicativos .NET Core que, em seguida, são analisados em um computador Windows
usando o PerfView.
Para obter mais informações sobre como instalar e começar a trabalhar com PerfCollect estão disponíveis no
GitHub.
Teste de carga e testes de estresse são importantes para garantir que um aplicativo web é eficaz e escalonável.
Suas metas são diferentes, mesmo que eles compartilham muitas vezes testes semelhantes.
Testes de carga: Testa se o aplicativo pode lidar com uma carga especificada de usuários para um determinado
cenário e ainda assim satisfazer a meta de resposta. O aplicativo é executado em condições normais.
Testes de estresse: Estabilidade do aplicativo de testes ao executar sob condições extremas e muitas vezes um
longo período de tempo:
Carga de usuário com altos – picos ou aumentando gradualmente.
Recursos de computação limitados.
Sob carga excessiva, pode o aplicativo se recuperar de falha e normalmente retornar ao comportamento
esperado? Sob carga excessiva, o aplicativo está não executado sob condições normais.
DevOps do Azure
Execuções de teste de carga podem ser iniciadas usando o planos de teste do Azure DevOps service.
O serviço suporta os seguintes tipos de formato de teste:
Teste do Visual Studio – teste da web criado no Visual Studio.
Teste com base em arquivo HTTP – tráfego HTTP capturado dentro do arquivo morto é reproduzida durante o
teste.
Teste com base na URL – permite especificar URLs para carregar testes, tipos de solicitação, cabeçalhos e
cadeias de caracteres de consulta. Executar a configuração de parâmetros, como duração, padrão de carga, o
número de usuários, etc., pode ser configurado.
Apache JMeter de teste.
Portal do Azure
Portal do Azure permite configurar e executar testes de carga de aplicativos Web, diretamente a partir da guia de
desempenho do serviço de aplicativo no portal do Azure.
O teste pode ser um teste manual com uma URL especificada, ou um arquivo de teste do Visual Studio Web, que
pode testar várias URLs.
No final do teste, os relatórios são gerados para mostrar as características de desempenho do aplicativo.
Estatísticas de exemplo incluem:
Tempo médio de resposta
Taxa de transferência máxima: solicitações por segundo
Percentual de falha
Ferramentas de terceiros
A lista a seguir contém as ferramentas de desempenho da web de terceiros com vários conjuntos de recursos:
Apache JMeter : Pacote de destaque completo de ferramentas de teste de carga. Limite de thread: precisa de
um thread por usuário.
AB - servidor HTTP Apache ferramenta de benchmark
Gatling : Ferramenta da área de trabalho com gravadores de GUI e de teste. Mais eficaz do que o JMeter.
Locust.IO : Não é limitado por threads.
Recursos adicionais
Série de blogs de teste de carga por Charles Sterling. Com data, mas a maioria dos tópicos ainda é relevante.
Globalização e localização no ASP.NET Core
13/11/2018 • 32 minutes 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
Exibir ou baixar código de exemplo (como baixar)
namespace Localization.Controllers
{
[Route("api/[controller]")]
public class AboutController : Controller
{
private readonly IStringLocalizer<AboutController> _localizer;
[HttpGet]
public string Get()
{
return _localizer["About Title"];
}
}
}
namespace Localization.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
{
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 IViewLocalizer fornece cadeias de caracteres localizadas para uma exibição. A classe 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.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.
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.
RootNamespaceAttribute
O atributo RootNamespace fornece o namespace raiz de um assembly quando o namespace raiz de um
assembly é diferente do nome do assembly.
Se o namespace raiz de um assembly é diferente do nome do assembly:
A localização não funciona por padrão.
A localização falha devido à maneira como os recursos são pesquisados dentro do assembly. RootNamespace é
um valor de tempo de build que não está disponível para o processo em execução.
Se o RootNamespace é diferente de AssemblyName , inclua o seguinte no AssemblyInfo.cs (com valores de
parâmetro substituídos pelos valores reais):
using System.Reflection;
using Microsoft.Extensions.Localization;
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
// 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 .
O formato do cookie é c=%LANGCODE%|uic=%LANGCODE% , em que c é Culture e uic é UICulture , por exemplo:
c=en-UK|uic=en-US
Se você especificar apenas as informações de cultura e a cultura da interface do usuário, a cultura especificada
será usada para as informações de cultura e para a cultura da interface do usuário.
O cabeçalho HTTP Accept-Language
O cabeçalho Accept-Language é configurável na maioria dos navegadores e originalmente foi criado para
especificar o idioma do usuário. Essa configuração indica que o navegador foi definido para enviar ou herdou do
sistema operacional subjacente. O cabeçalho HTTP Accept-Language de uma solicitação do navegador não é
uma maneira infalível de detectar o idioma preferencial do usuário (consulte Definindo preferências de idioma
em um navegador). Um aplicativo de produção deve incluir uma maneira para que um usuário personalize sua
opção de cultura.
Definir o cabeçalho HTTP Accept-Language no IE
1. No ícone de engrenagem, toque em Opções da Internet.
2. Toque em Idiomas.
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.
NOTE
Talvez você não consiga inserir vírgulas decimais em campos decimais. Para dar suporte à validação do jQuery para
localidades de idiomas diferentes do inglês que usam uma vírgula (“,”) para um ponto decimal e formatos de data diferentes
do inglês dos EUA, você deve tomar medidas para globalizar o aplicativo. Veja Problema 4076 do GitHub para obter
instruções sobre como adicionar casas decimais.
Recursos adicionais
O projeto Localization.StarterWeb usado no artigo.
Globalizando e localizando aplicativos do .NET
Recursos em arquivos .resx
Kit de Ferramentas de Aplicativo Multilíngue da Microsoft
Configurar a localização de objeto portátil no
ASP.NET Core
30/10/2018 • 12 minutes 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:
NOTE
A reconfiguração de URL pode reduzir o desempenho de um aplicativo. Sempre que possível, limite o número e a
complexidade das regras.
Pacote
Para incluir o middleware em seu projeto, adicione uma referência de pacote ao metapacote
Microsoft.AspNetCore.App no arquivo de projeto, que contém o pacote Microsoft.AspNetCore.Rewrite.
Quando não estiver usando o metapacote Microsoft.AspNetCore.App , adicione uma referência de projeto ao
pacote Microsoft.AspNetCore.Rewrite .
Extensão e opções
Estabeleça 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 de reconfiguração. 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 UseRewriter:
app.UseRewriter(options);
}
app.UseStaticFiles();
app.UseRewriter(options);
}
app.UseStaticFiles();
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 não há
nenhuma correspondência de regras no aplicativo de exemplo na URL de redirecionamento:
A segunda solicitação recebe uma resposta 200 – OK do aplicativo.
O corpo da resposta mostra a URL de redirecionamento.
Uma viagem de ida e volta é feita para o servidor quando uma URL é redirecionada.
WARNING
Tenha cuidado ao estabelecer 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 .
O exemplo a seguir mostra como definir o código de status como 301 –Movido Permanentemente e alterar a
porta para 5001.
app.UseRewriter(options);
}
Use AddRedirectToHttpsPermanent para redirecionar solicitações não seguras para o mesmo host e caminho
com o protocolo HTTPS seguro na porta 443. O middleware define o código de status como 301 – Movido
Permanentemente.
app.UseRewriter(options);
}
NOTE
Ao redirecionar para um ponto de extremidade seguro sem a necessidade de regras de redirecionamento adicionais,
recomendamos o uso do Middleware de Redirecionamento HTTPS. Para obter mais informações, veja o tópico Impor
HTTPS.
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.
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
O acento circunflexo ( ^ ) no início da expressão significa que a correspondência começa no início do caminho
da URL.
No exemplo anterior com a regra de redirecionamento, redirect-rule/(.*) , não há nenhum acento circunflexo (
^ ) no início do regex. Portanto, qualquer caractere pode preceder redirect-rule/ no caminho para uma
correspondência com êxito.
CAMINHO CORRESPONDER A
/redirect-rule/1234/5678 Sim
/my-cool-redirect-rule/1234/5678 Sim
/anotherredirect-rule/1234/5678 Sim
CAMINHO CORRESPONDER A
/rewrite-rule/1234/5678 Sim
/my-cool-rewrite-rule/1234/5678 Não
/anotherrewrite-rule/1234/5678 Não
NOTE
Use skipRemainingRules: true sempre que possível, porque as regras de correspondência são computacionalmente
caras e aumentam o tempo de resposta do aplicativo. Para a resposta mais rápida do aplicativo:
Ordene as regras de reconfiguração da regra com correspondência mais frequente para a regra com correspondência
menos frequente.
Ignore o processamento das regras restantes quando ocorrer uma correspondência e nenhum processamento de regra
adicional for necessário.
mod_rewrite do Apache
Aplique as regras do mod_rewrite do Apache com AddApacheModRewrite. Verifique se o arquivo de regras foi
implantado com o aplicativo. Para obter mais informações e exemplos de regras de mod_rewrite, consulte
mod_rewrite do Apache.
Um StreamReader é usado para ler as regras do arquivo de regras ApacheModRewrite.txt:
public void Configure(IApplicationBuilder app)
{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.UseRewriter(options);
}
app.UseStaticFiles();
<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>
Caso você tenha um Módulo de Reconfiguração do IIS ativo com regras no nível do servidor configuradas que
poderiam afetar o aplicativo de maneiras indesejadas, desabilite o Módulo de Reconfiguração do IIS em um
aplicativo. Para obter mais informações, consulte Desabilitando módulos do IIS.
Recursos sem suporte
O middleware liberado com o ASP.NET Core 2.x não dá suporte aos seguintes recursos do Módulo de
Reconfiguração de URL do IIS:
Regras de saída
Variáveis de servidor personalizadas
Curingas
LogRewrittenUrl
Variáveis de servidor compatíveis
O middleware dá suporte às seguintes variáveis de servidor do Módulo de Reconfiguração de URL do IIS:
CONTENT_LENGTH
CONTENT_TYPE
HTTP_ACCEPT
HTTP_CONNECTION
HTTP_COOKIE
HTTP_HOST
HTTP_REFERER
HTTP_URL
HTTP_USER_AGENT
HTTPS
LOCAL_ADDR
QUERY_STRING
REMOTE_ADDR
REMOTE_PORT
REQUEST_FILENAME
REQUEST_URI
NOTE
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.
REWRITECONTEXT.RESULT AÇÃO
app.UseRewriter(options);
}
app.UseStaticFiles();
O aplicativo de exemplo demonstra um método que redireciona as solicitações para caminhos que terminam
com .xml. Se uma solicitação for feita para /file.xml , ela será redirecionada para /xmlfiles/file.xml . O código
de status é definido como 301 – Movido Permanentemente. Quando o navegador faz uma nova solicitação para
/xmlfiles/file.xml, o Middleware de Arquivo Estático fornece o arquivo para o cliente por meio da pasta
wwwroot/xmlfiles. Para um redirecionamento, defina explicitamente o código de status da resposta. Caso
contrário, um código de status 200 – OK será retornado e o redirecionamento não ocorrerá no cliente.
RewriteRules.cs:
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);
}
app.UseStaticFiles();
RewriteRules.cs:
if (request.Path.Value.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
{
context.Result = RuleResult.SkipRemainingRules;
request.Path = "/file.txt";
}
}
app.UseRewriter(options);
}
app.UseStaticFiles();
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 uma solicitação é feita para image.png, a solicitação é
redirecionada para /png-images/image.png . Se uma solicitação é feita para image.jpg, a solicitação é
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 "/"
Provedores de arquivos no ASP.NET Core
22/11/2018 • 14 minutes to read • Edit Online
IMPLEMENTAÇÃO DESCRIÇÃO
PhysicalFileProvider
O PhysicalFileProvider fornece acesso ao sistema de arquivos físico. O PhysicalFileProvider usa o tipo
System.IO.File (para o provedor físico) e delimita todos os caminhos para um diretório e seus filhos. Esse escopo
impede o acesso ao sistema de arquivos fora do diretório especificado e seus filhos. Ao criar uma instância para
esse provedor, um caminho de diretório é necessário e serve como o caminho base para todas as solicitações feitas
usando o provedor. Você pode criar uma instância de um provedor PhysicalFileProvider diretamente, ou pode
solicitar um IFileProvider em um construtor por meio de uma injeção de dependência.
Tipos estáticos
O código a seguir mostra como criar um PhysicalFileProvider e usá-lo para obter o conteúdo do diretório e as
informações do arquivo:
O provedor de arquivos pode ser usado para percorrer o diretório especificado por applicationRoot ou chamar
GetFileInfo para obter as informações de um arquivo. O provedor de arquivos não tem acesso fora do diretório
applicationRoot .
<ul>
@foreach (var item in Model.DirectoryContents)
{
if (item.IsDirectory)
{
<li><strong>@item.Name</strong></li>
}
else
{
<li>@item.Name - @item.Length bytes</li>
}
}
</ul>
No aplicativo de amostra, a classe HomeController recebe uma instância IFileProvider para obter o conteúdo do
diretório para o caminho base do aplicativo.
Controllers/HomeController.cs:
return View(contents);
}
}
ManifestEmbeddedFileProvider
O ManifestEmbeddedFileProvider é usado para acessar arquivos inseridos em assemblies. O
ManifestEmbeddedFileProvider usa um manifesto compilado no assembly para reconstruir os caminhos originais
dos arquivos inseridos.
NOTE
O ManifestEmbeddedFileProvider está disponível no ASP.NET Core 2.1 ou posterior. Para acessar arquivos inseridos em
assemblies no ASP.NET Core 2.0 ou anterior, confira a versão ASP.NET Core 1.x deste tópico.
Para gerar um manifesto dos arquivos inseridos, defina a propriedade <GenerateEmbeddedFilesManifest> como
true . Especifique os arquivos para inserir com <EmbeddedResource>:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resource.txt" />
</ItemGroup>
</Project>
Use padrões glob para especificar um ou mais arquivos a serem inseridos no assembly.
O aplicativo de amostra cria um ManifestEmbeddedFileProvider e passa o assembly atualmente em execução para
seu construtor.
Startup.cs:
var manifestEmbeddedProvider =
new ManifestEmbeddedFileProvider(Assembly.GetEntryAssembly());
SOBRECARGA DESCRIÇÃO
EmbeddedFileProvider
O EmbeddedFileProvider é usado para acessar arquivos inseridos em assemblies. Especifique os arquivos para
inserir na propriedade <EmbeddedResource> no arquivo do projeto:
<ItemGroup>
<EmbeddedResource Include="Resource.txt" />
</ItemGroup>
Use padrões glob para especificar um ou mais arquivos a serem inseridos no assembly.
O aplicativo de amostra cria um EmbeddedFileProvider e passa o assembly atualmente em execução para seu
construtor.
Startup.cs:
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 . . No aplicativo de amostra, o baseNamespace é
FileProviderSample. .
services.AddSingleton<IFileProvider>(compositeProvider);
services.AddSingleton<IFileProvider>(compositeProvider);
Monitorar as alterações
O método IFileProvider.Watch proporciona um cenário para monitorar um ou mais arquivos ou diretórios quanto
a alterações. Watch aceita uma cadeia de caracteres de caminho, que pode usar padrões glob para especificar
vários arquivos. Watch retorna um IChangeToken. O token de alteração expõe:
HasChanged: uma propriedade que pode ser inspecionada para determinar se uma alteração ocorreu.
RegisterChangeCallback: chamado quando são detectadas alterações na cadeia de caracteres do caminho
especificado. Cada token de alteração chama apenas seu retorno de chamada associado em resposta a uma
única alteração. Para permitir o monitoramento constante, use um TaskCompletionSource (mostrado abaixo) ou
recrie instâncias de IChangeToken em resposta a alterações.
No aplicativo de amostra, o aplicativo de console WatchConsole é 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");
}
private static PhysicalFileProvider _fileProvider =
new PhysicalFileProvider(Directory.GetCurrentDirectory());
while (true)
{
MainAsync().GetAwaiter().GetResult();
}
}
token.RegisterChangeCallback(state =>
((TaskCompletionSource<object>)state).TrySetResult(null), tcs);
await tcs.Task.ConfigureAwait(false);
Console.WriteLine("quotes.txt changed");
}
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_POLLING_FILE_WATCHER como
1 ou true para sondar o sistema de arquivos a cada quatro segundos em relação a alterações.
Padrões glob
Os caminhos do sistema de arquivos usam padrões curinga chamados padrões glob (ou globbing ). Especifique
grupos de arquivos com esses padrões. Os dois caracteres curinga são * e ** :
*
Corresponde a qualquer coisa no nível da pasta atual, 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 glob
directory/file.txt
Corresponde a um arquivo específico em um diretório específico.
directory/*.txt
Corresponde a todos os arquivos com a extensão .txt em um diretório específico.
directory/*/appsettings.json
Corresponde a todos os arquivos appsettings.json em diretórios que estão exatamente um nível abaixo da pasta
diretório.
directory/**/*.txt
Corresponde a todos os arquivos com a extensão .txt encontrados em qualquer lugar abaixo da pasta diretório.
Solicitar recursos no ASP.NET Core
25/06/2018 • 5 minutes to read • Edit Online
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.
NOTE
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)
Acessar o HttpContext no ASP.NET Core
04/02/2019 • 2 minutes to read • Edit Online
@{
var username = Context.User.Identity.Name;
}
return View();
}
}
No exemplo a seguir:
o UserRepository declara sua dependência no IHttpContextAccessor .
A dependência é fornecida quando a injeção de dependência resolve a cadeia de dependências e cria uma
instância do UserRepository .
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 (ASP.NET
Core 2.1 ou posterior), veja 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, veja 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<IConfigurationMonitor>(
() => config.GetReloadToken(),
InvokeChanged,
this);
}
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
string message = $"State updated at {DateTime.Now}";
_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.
Recursos adicionais
Memória de cache no ASP.NET Core
O cache no ASP.NET Core distribuído
Cache de resposta no ASP.NET Core
Middleware no ASP.NET Core de cache de resposta
Auxiliar de Marca de Cache no ASP.NET Core MVC
Auxiliar de Marca de Cache Distribuído no ASP.NET Core
OWIN (Open Web Interface para .NET) com o
ASP.NET Core
10/01/2019 • 9 minutes to read • Edit Online
NOTE
O uso desses adaptadores implica um custo de desempenho. Os aplicativos que usam somente componentes do ASP.NET
Core não devem usar o pacote Microsoft.AspNetCore.Owin ou os adaptadores.
NOTE
Os cabeçalhos de resposta devem ser modificados apenas antes da primeira gravação no fluxo de resposta.
NOTE
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(async (next) =>
{
// do something before
await OwinHello(new OwinEnvironment(HttpContext));
// 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();
}
}
}
O IServer é uma interface que requer 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>();
});
}
Com isso em vigor, invoque a extensão no Program.cs para executar um aplicativo ASP.NET Core usando este
servidor personalizado:
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
Você pode construir 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
Tarefas em segundo plano com serviços
hospedados no ASP.NET Core
12/12/2018 • 9 minutes to read • Edit Online
Pacote
Referencie o metapacote Microsoft.AspNetCore.App ou adicione uma referência de pacote ao pacote
Microsoft.Extensions.Hosting.
Interface IHostedService
Os serviços hospedados implementam a interface IHostedService. A interface define dois métodos para objetos
que são gerenciados pelo host:
StartAsync(CancellationToken) – StartAsync contém a lógica para iniciar a tarefa em segundo plano. Ao
usar o host Web, StartAsync é chamado depois que o servidor é iniciado e
IApplicationLifetime.ApplicationStarted é disparado. Ao usar o host genérico, StartAsync é chamado
antes de ApplicationStarted ser disparado.
StopAsync(CancellationToken) – é disparado quando o host está executando um desligamento normal.
StopAsync contém a lógica para encerrar a tarefa em segundo plano. Implemente o IDisposable e os
finalizadores (destruidores) para descartar todos os recursos não gerenciados.
O token de cancelamento tem um tempo limite padrão de cinco segundos para indicar que o processo de
desligamento não deve mais ser normal. Quando for solicitado um cancelamento no token:
Todas as demais operações em segundo plano que o aplicativo estiver executando deverão ser
anuladas.
Todos os métodos chamados em StopAsync deverão retornar imediatamente.
No entanto, as tarefas não são abandonadas após a solicitação de cancelamento—o chamador aguarda a
conclusão de todas as tarefas.
Se o aplicativo for desligado inesperadamente (por exemplo, em uma falha do processo do aplicativo),
StopAsync não poderá ser chamado. Portanto, os métodos chamados ou operações realizadas em
StopAsync talvez não ocorram.
return Task.CompletedTask;
}
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
O serviço hospedado cria um escopo para resolver o serviço da tarefa em segundo plano com escopo para
chamar seu método DoWork :
internal class ConsumeScopedServiceHostedService : IHostedService
{
private readonly ILogger _logger;
DoWork();
return Task.CompletedTask;
}
scopedProcessingService.DoWork();
}
}
return Task.CompletedTask;
}
}
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
_workItems.Enqueue(workItem);
_signal.Release();
}
return workItem;
}
}
No QueueHostedService , as tarefas em segundo plano na fila são removidas da fila e executadas como um
BackgroundService, que é uma classe base para implementar um IHostedService de longa execução:
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
Quando o botão Adicionar Tarefa é selecionado na página de índice, o método OnPostAddTask é executado. O
QueueBackgroundWorkItem é chamado para enfileirar o item de trabalho:
public IActionResult OnPostAddTask()
{
Queue.QueueBackgroundWorkItem(async token =>
{
var guid = Guid.NewGuid().ToString();
_logger.LogInformation(
$"Queued Background Task {guid} is complete. 3/3");
});
return RedirectToPage();
}
Recursos adicionais
Implementar tarefas em segundo plano em microsserviços com IHostedService e a classe
BackgroundService
System.Threading.Timer
Usar assemblies de inicialização de hospedagem no
ASP.NET Core
10/01/2019 • 28 minutes to read • Edit Online
Atributo HostingStartup
Um atributo HostingStartup indica a presença de um assembly de inicialização de hospedagem para ativar em
tempo de execução.
O assembly de entrada ou o assembly que contém a classe Startup é automaticamente examinado para o
atributo HostingStartup . A lista de assemblies a ser pesquisada para os atributos HostingStartup é carregada
no tempo de execução da configuração em WebHostDefaults.HostingStartupAssembliesKey. A lista de
assemblies para excluir da descoberta é carregada de WebHostDefaults.HostingStartupExcludeAssembliesKey.
Para obter mais informações, confira Host da Web: Hospedando assemblies de inicialização e Host da Web:
hospedando assemblies de exclusão de inicialização.
No exemplo a seguir, o namespace do assembly de inicialização de hospedagem é StartupEnhancement . A classe
que contém o código de inicialização de hospedagem é StartupEnhancementHostingStartup :
[assembly: HostingStartup(typeof(StartupEnhancement.StartupEnhancementHostingStartup))]
Projeto
Crie uma inicialização de hospedagem com qualquer um dos seguintes tipos de projeto:
Biblioteca de classes
Aplicativo de console sem um ponto de entrada
Biblioteca de classes
Uma melhoria da inicialização de hospedagem pode ser fornecida em uma biblioteca de classes. A biblioteca
contém um atributo HostingStartup .
O código de exemplo inclui um aplicativo Razor Pages, HostingStartupApp e uma biblioteca de classes,
HostingStartupLibrary. A biblioteca de classes:
Contém uma classe de inicialização de hospedagem, ServiceKeyInjection , que implementa
IHostingStartup . ServiceKeyInjection adiciona um par de cadeias de caracteres de serviço à configuração
do aplicativo usando o provedor de configuração na memória (AddInMemoryCollection).
Inclui um atributo HostingStartup que identifica o namespace e a classe de inicialização de hospedagem.
O método Configure da classe ServiceKeyInjection usa um IWebHostBuilder para adicionar melhorias a um
aplicativo. IHostingStartup.Configure no assembly de inicialização de hospedagem é chamado pelo tempo de
execução antes de Startup.Configure no código do usuário, o que permite que o código de usuário substitua
qualquer configuração fornecida pelo assembly de inicialização de hospedagem.
HostingStartupLibrary/ServiceKeyInjection.cs:
[assembly: HostingStartup(typeof(HostingStartupLibrary.ServiceKeyInjection))]
namespace HostingStartupLibrary
{
public class ServiceKeyInjection : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(config =>
{
var dict = new Dictionary<string, string>
{
{"DevAccount_FromLibrary", "DEV_1111111-1111"},
{"ProdAccount_FromLibrary", "PROD_2222222-2222"}
};
config.AddInMemoryCollection(dict);
});
}
}
}
A página de índice do aplicativo lê e renderiza os valores de configuração para as duas chaves definidas pelo
assembly de inicialização de hospedagem da biblioteca de classes:
HostingStartupApp/Pages/Index.cshtml.cs:
O código de exemplo também inclui um projeto de pacote do NuGet que fornece uma inicialização de
hospedagem separada, HostingStartupPackage. O pacote tem as mesmas características da biblioteca de
classes descrita anteriormente. O pacote:
Contém uma classe de inicialização de hospedagem, ServiceKeyInjection , que implementa
IHostingStartup . ServiceKeyInjection adiciona um par de cadeias de caracteres de serviço para a
configuração do aplicativo.
Inclui um atributo HostingStartup .
HostingStartupPackage/ServiceKeyInjection.cs:
[assembly: HostingStartup(typeof(HostingStartupPackage.ServiceKeyInjection))]
namespace HostingStartupPackage
{
public class ServiceKeyInjection : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(config =>
{
var dict = new Dictionary<string, string>
{
{"DevAccount_FromPackage", "DEV_3333333-3333"},
{"ProdAccount_FromPackage", "PROD_4444444-4444"}
};
config.AddInMemoryCollection(dict);
});
}
}
}
A página de índice do aplicativo lê e renderiza os valores de configuração para as duas chaves definidas pelo
assembly de inicialização de hospedagem do pacote:
HostingStartupApp/Pages/Index.cshtml.cs:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions"
Version="2.1.1" />
</ItemGroup>
</Project>
Um atributo HostingStartup identifica uma classe como uma implementação de IHostingStartup para o
carregamento e a execução durante a criação do IWebHost. No seguinte exemplo, o namespace é
StartupEnhancement e a classe é StartupEnhancementHostingStartup :
[assembly: HostingStartup(typeof(StartupEnhancement.StartupEnhancementHostingStartup))]
Uma classe implementa IHostingStartup . O método Configure da classe usa um IWebHostBuilder para
adicionar melhorias a um aplicativo. IHostingStartup.Configure no assembly de inicialização de hospedagem é
chamado pelo tempo de execução antes de Startup.Configure no código do usuário, o que permite que o
código de usuário substitua qualquer configuração fornecida pelo assembly de inicialização de hospedagem.
namespace StartupEnhancement
{
public class StartupEnhancementHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
// Use the IWebHostBuilder to add app enhancements.
}
}
}
HostingStartupLibrary;HostingStartupPackage;StartupDiagnostics
Um assembly de inicialização de hospedagem também pode ser definido usando a configuração do host
Assemblies de inicialização de hospedagem.
Quando há vários assemblies de inicialização de hospedagem, os métodos Configure são executados na ordem
em que os assemblies são listados.
Ativação
As opções para ativação da inicialização de hospedagem são:
Repositório de tempo de execução – A ativação não requer uma referência de tempo de compilação para a
ativação. O aplicativo de exemplo coloca os arquivos de dependências e o assembly de inicialização de
hospedagem em uma pasta, implantação, para facilitar a implantação da inicialização de hospedagem em
um ambiente multicomputador. A pasta implantação também inclui um script do PowerShell que cria ou
modifica variáveis de ambiente no sistema de implantação para habilitar a inicialização de hospedagem.
Referência de tempo de compilação necessária para a ativação
Pacote do NuGet
Pasta Lixeira do projeto
Repositório de tempo de execução
A implementação de inicialização de hospedagem é colocada no repositório de tempo de execução. Uma
referência de tempo de compilação para o assembly não é exigida pelo aplicativo aprimorado.
Depois que a inicialização de hospedagem é compilada, um repositório de tempo de execução é gerado, usando
o arquivo de projeto do manifesto e o comando do dotnet store.
dotnet store --manifest {MANIFEST FILE} --runtime {RUNTIME IDENTIFIER} --output {OUTPUT LOCATION} --skip-
optimization
Para o tempo de execução descobrir o repositório de tempo de execução, o local do repositório de tempo de
execução é adicionado à variável de ambiente DOTNET_SHARED_STORE .
Modificar e colocar o arquivo de dependências da inicialização de hospedagem
Para ativar o aprimoramento sem uma referência de pacote ao aprimoramento, especifique as dependências
adicionais do tempo de execução com additionalDeps . additionalDeps permite que você:
Amplie o grafo de biblioteca do aplicativo, fornecendo um conjunto de arquivos *.deps.json adicionais a
serem mesclados com o arquivo *.deps.json próprio do aplicativo na inicialização.
Torne o assembly de inicialização de hospedagem detectável e carregável.
A abordagem recomendada para gerar o arquivo de dependências adicionais é:
1. Executar o dotnet publish no arquivo de manifesto do repositório de tempo de execução mencionado na
seção anterior.
2. Remover a referência do manifesto das bibliotecas e a seção runtime resultantes do arquivo *deps.json.
additionalDeps/shared/Microsoft.AspNetCore.App/2.1.0/StartupDiagnostics.deps.json
Para o tempo de execução descobrir o local do repositório de tempo de execução, o local do arquivo de
dependências adicionais é adicionado à variável de ambiente DOTNET_ADDITIONAL_DEPS .
No aplicativo de exemplo (projeto RuntimeStore), crie o repositório de tempo de execução e gere o arquivo de
dependências adicionais usando um script PowerShell.
Para obter exemplos de como definir variáveis de ambiente para vários sistemas operacionais, confira Usar
vários ambientes.
Implantação
Para facilitar a implantação de uma inicialização de hospedagem em um ambiente multicomputador, o
aplicativo de exemplo cria uma pasta implantação na saída publicada que contém:
O repositório de tempo de execução de inicialização de hospedagem.
O arquivo de dependências de inicialização de hospedagem.
Um script do PowerShell que cria ou modifica ASPNETCORE_HOSTINGSTARTUPASSEMBLIES , DOTNET_SHARED_STORE e
DOTNET_ADDITIONAL_DEPS para dar suporte à ativação da inicialização de hospedagem. Execute o script de um
prompt de comando do PowerShell administrativo no sistema de implantação.
Pacote NuGet
Uma melhoria da inicialização de hospedagem pode ser fornecida em pacote do NuGet. O pacote tem um
atributo HostingStartup . Os tipos de inicialização de hospedagem fornecidos pelo pacote são disponibilizados
para o aplicativo usando qualquer uma das seguintes abordagens:
O arquivo de projeto do aplicativo aprimorado faz uma referência de pacote para a inicialização de
hospedagem no arquivo de projeto do aplicativo (uma referência de tempo de compilação). Com a
referência de tempo de compilação em vigor, o assembly de inicialização de hospedagem e todas as suas
dependências são incorporados ao arquivo de dependência do aplicativo (*.deps.json). Essa abordagem se
aplica a um pacote de assembly de inicialização de hospedagem publicado para nuget.org.
O arquivo de dependências da inicialização de hospedagem fica disponível para o aplicativo avançado,
conforme descrito na seção Repositório de tempo de execução (sem uma referência de tempo de
compilação).
Para obter mais informações sobre pacotes do NuGet e o repositório de tempo de execução, consulte os
tópicos a seguir:
Como criar um pacote do NuGet com ferramentas de plataforma cruzada
Publicando pacotes
Repositório de pacote de tempo de execução
Pasta Lixeira do projeto
Uma melhoria da inicialização de hospedagem pode ser fornecida por um assemby implantado na lixeira no
aplicativo aprimorado. Os tipos de inicialização de hospedagem fornecidos pelo assembly são disponibilizados
para o aplicativo usando uma das seguintes abordagens:
O arquivo de projeto do aplicativo aprimorado faz uma referência de assembly para a inicialização de
hospedagem (uma referência de tempo de compilação). Com a referência de tempo de compilação em vigor,
o assembly de inicialização de hospedagem e todas as suas dependências são incorporados ao arquivo de
dependência do aplicativo (*.deps.json). Essa abordagem é aplicável quando o cenário de implantação faz
chamadas para mover o assembly compilado da biblioteca de inicialização de hospedagem (arquivo DLL )
para o projeto consumidor ou para um local acessível pelo projeto consumidor e uma referência de tempo
de compilação é feita para o assembly de inicialização de hospedagem.
O arquivo de dependências da inicialização de hospedagem fica disponível para o aplicativo avançado,
conforme descrito na seção Repositório de tempo de execução (sem uma referência de tempo de
compilação).
Código de exemplo
O código de exemplo (como baixar) demonstra cenários de implementação de inicialização de hospedagem:
Dois assemblies de inicialização de hospedagem (bibliotecas de classes) definem um par chave-valor de
configuração na memória cada:
Pacote do NuGet (HostingStartupPackage)
Biblioteca de classes (HostingStartupLibrary)
Uma inicialização de hospedagem é ativada de um assembly implantado pelo repositório de tempo de
execução (StartupDiagnostics). O assembly adiciona dois middlewares ao aplicativo na inicialização que
fornecem informações de diagnóstico sobre:
Serviços registrados
Endereço (esquema, host, base do caminho, caminho, cadeia de caracteres de consulta)
Conexão (IP remoto, porta remota, IP local, porta local, certificado do cliente)
Cabeçalhos de solicitação
Variáveis de ambiente
Para executar a amostra:
Ativação de um pacote do NuGet
1. Compile o pacote HostingStartupPackage com o comando dotnet pack.
2. Adicione o nome do assembly do pacote do HostingStartupPackage para a variável de ambiente
ASPNETCORE_HOSTINGSTARTUPASSEMBLIES .
3. Compile e execute o aplicativo. Uma referência de pacote está presente no aplicativo aprimorado (uma
referência de tempo de compilação). Um <PropertyGroup> no arquivo de projeto do aplicativo especifica
a saída do projeto de pacote (../HostingStartupPackage/bin/Debug) como uma origem de pacote. Isso
permite que o aplicativo use o pacote sem carregar o pacote para nuget.org. Para mais informações,
consulte as notas no arquivo de projeto do HostingStartupApp.
<PropertyGroup>
<RestoreSources>$(RestoreSources);https://api.nuget.org/v3/index.json;../HostingStartupPackage/bin/De
bug</RestoreSources>
</PropertyGroup>
4. Observe que os valores de chave de configuração do serviço renderizados pela página de índice
correspondem aos valores definidos pelo método ServiceKeyInjection.Configure do pacote.
Se você fizer alterações no projeto HostingStartupPackage e recompilá-lo, limpe os caches de pacote do NuGet
locais para garantir que o HostingStartupApp receba o pacote atualizado e não um pacote obsoleto do cache
local. Para limpar os caches locais do NuGet, execute o seguinte comando dotnet nuget locals:
3. A pasta lixeira implanta o assembly da biblioteca de classes para o aplicativo ao copiar o arquivo
HostingStartupLibrary.dll da saída compilada da biblioteca de classes para a pasta lixeira/Depurar do
aplicativo.
4. Compile e execute o aplicativo. Um <ItemGroup> do arquivo de projeto do aplicativo referencia o
assembly da biblioteca de classes (.\lixeira\Depurar\netcoreapp2.1\HostingStartupLibrary.dll) (uma
referência de tempo de compilação). Para mais informações, consulte as notas no arquivo de projeto do
HostingStartupApp.
<ItemGroup>
<Reference Include=".\bin\Debug\netcoreapp2.1\HostingStartupLibrary.dll">
<HintPath>.\bin\Debug\netcoreapp2.1\HostingStartupLibrary.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
</Reference>
</ItemGroup>
5. Observe que os valores de chave de configuração do serviço renderizados pela página de índice
correspondem aos valores definidos pelo método ServiceKeyInjection.Configure da biblioteca de
classes.
Ativação de um assembly implantado pelo repositório de tempo de execução
1. O projeto StartupDiagnostics usa o PowerShell para modificar seu arquivo StartupDiagnostics.deps.json.
O PowerShell é instalado por padrão em um sistema operacional Windows começando no Windows 7
SP1 e no Windows Server 2008 R2 SP1. Para obter o PowerShell em outras plataformas, confira
Instalando o Windows PowerShell.
2. Compilar o projeto StartupDiagnostics. Depois que o projeto é compilado, um destino de compilação no
arquivo de projeto automaticamente:
Dispara o script do PowerShell para modificar o arquivo StartupDiagnostics.deps.json.
Move o arquivo StartupDiagnostics.deps.json para a pasta additionalDeps do perfil do usuário.
3. Execute o comando dotnet store em um prompt de comando no diretório de inicialização de
hospedagem para armazenar o assembly e suas dependências no repositório de tempo de execução do
perfil do usuário:
Para Windows, o comando usa o win7-x64 RID (identificador de tempo de execução). Ao fornecer a
inicialização de hospedagem para um tempo de execução diferente, substitua o RID correto.
4. Defina as variáveis de ambiente:
Adicione o nome do assembly de StartupDiagnostics para a variável de ambiente
ASPNETCORE_HOSTINGSTARTUPASSEMBLIES .
No Windows, defina a variável de ambiente DOTNET_ADDITIONAL_DEPS como
%UserProfile%\.dotnet\x64\additionalDeps\StartupDiagnostics\ . No macOS/Linux, defina a variável de
ambiente DOTNET_ADDITIONAL_DEPS como
/Users/<USER>/.dotnet/x64/additionalDeps/StartupDiagnostics/ , em que <USER> é o perfil do usuário
que contém a inicialização de hospedagem.
5. Execute o aplicativo de exemplo.
6. Solicite o ponto de extremidade /services para ver os serviços registrados do aplicativo. Solicite o
ponto de extremidade /diag para ver as informações de diagnóstico.
Metapacote
Microsoft.AspNetCore.App para
ASP.NET Core 2.1
08/10/2018 • 7 minutes to read • Edit Online
Este recurso exige o ASP.NET Core 2.1 e posterior direcionado ao .NET Core
2.1 e posterior.
O metapacote Microsoft.AspNetCore.App para ASP.NET Core:
Não tem dependências de terceiros, com exceção de Json.NET,
Remotion.Linq e IX-Async. Essas dependências de terceiros são
consideradas necessárias para garantir o funcionamento dos principais
recursos das estruturas.
Inclui todos os pacotes com suporte pela equipe do ASP.NET Core, exceto
aqueles que contêm dependências de terceiros (que não sejam aqueles
mencionados anteriormente).
Inclui todos os pacotes com suporte pela equipe do Entity Framework
Core, exceto aqueles que contêm dependências de terceiros (que não sejam
aqueles mencionados anteriormente).
Todos os recursos do ASP.NET Core 2.1 e posterior e do Entity Framework
Core 2.1 e posterior são incluídos no pacote Microsoft.AspNetCore.App . Os
modelos de projeto padrão direcionados para ASP.NET Core 2.1 e posterior
usam este pacote. Recomendamos que aplicativos voltados para o ASP.NET
Core 2.1 e posterior e o Entity Framework Core 2.1 e posterior usem o pacote
Microsoft.AspNetCore.App .
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
NOTE
Recomendamos que os aplicativos direcionados ao ASP.NET Core 2.1 e posterior usem o metapacote
Microsoft.AspNetCore.App em vez desse pacote. Veja Migração do Microsoft.AspNetCore.All para
Microsoft.AspNetCore.App neste artigo.
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.
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>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
</ItemGroup>
</Project>
Microsoft.AspNetCore.ApplicationInsights.HostingStartup
Microsoft.AspNetCore.AzureAppServices.HostingStartup
Microsoft.AspNetCore.AzureAppServicesIntegration
Microsoft.AspNetCore.DataProtection.AzureKeyVault
Microsoft.AspNetCore.DataProtection.AzureStorage
Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv
Microsoft.AspNetCore.SignalR.Redis
Microsoft.Data.Sqlite
Microsoft.Data.Sqlite.Core
Microsoft.EntityFrameworkCore.Sqlite
Microsoft.EntityFrameworkCore.Sqlite.Core
Microsoft.Extensions.Caching.Redis
Microsoft.Extensions.Configuration.AzureKeyVault
Microsoft.Extensions.Logging.AzureAppServices
Microsoft.VisualStudio.Web.BrowserLink
Para mover de Microsoft.AspNetCore.All para Microsoft.AspNetCore.App , se seu aplicativo usa APIs dos
pacotes acima ou de pacotes trazidos por eles, adicione referências a eles em seu projeto.
Todas as dependências dos pacotes anteriores que, de outra forma, não são dependências de
Microsoft.AspNetCore.App não são incluídas implicitamente. Por exemplo:
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).
A cadeia de caracteres fornecida para o método Define é um modelo e não uma cadeia de caracteres interpolada.
Os espaços reservados são preenchidos na ordem em que os tipos são especificados. Os nomes do espaço
reservado no modelo devem ser descritivos e consistentes em todos os modelos. Eles servem como nomes de
propriedade em dados de log estruturado. Recomendamos o uso da formatação Pascal Case para nomes de
espaço reservado. Por exemplo, {Count} , {FirstName} .
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):
_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 :
public static void QuoteAdded(this ILogger logger, string quote)
{
_quoteAdded(logger, quote, null);
}
_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 QuoteDeleted(this ILogger logger, string quote, int id)
{
_quoteDeleted(logger, quote, id, null);
}
public static void QuoteDeleteFailed(this ILogger logger, int id, Exception ex)
{
_quoteDeleteFailed(logger, id, ex);
}
No modelo da página de Índice, uma exclusão de aspas bem-sucedida chama o método QuoteDeleted no agente.
Quando as aspas não são encontradas para exclusão, uma ArgumentNullException é gerada. A exceção é
interceptada pela instrução try – catch e registrada em log com uma chamada ao método QuoteDeleteFailed no
agente no bloco catch (Pages/Index.cshtml.cs):
_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
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).
Como é o caso com o método Define , a cadeia de caracteres fornecida ao método DefineScope é um modelo e
não uma cadeia de caracteres interpolada. Os espaços reservados são preenchidos na ordem em que os tipos são
especificados. Os nomes do espaço reservado no modelo devem ser descritivos e consistentes em todos os
modelos. Eles servem como nomes de propriedade em dados de log estruturado. Recomendamos o uso da
formatação Pascal Case para nomes de espaço reservado. Por exemplo, {Count} , {FirstName} .
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.
{
"Logging": {
"Console": {
"IncludeScopes": true
},
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
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)
Recursos adicionais
Registro em log
Desenvolver aplicativos ASP.NET Core usando um
observador de arquivo
16/01/2019 • 6 minutes to read • Edit Online
dotnet run
O resultado do console mostra mensagens semelhantes à seguinte (indicando que o aplicativo está em execução e
aguarda solicitações):
$ dotnet run
Hosting environment: Development
Content root path: C:/Docs/aspnetcore/tutorials/dotnet-watch/sample/WebApp
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
dotnet restore
Executar os comandos da CLI do .NET Core usando dotnet watch
Qualquer comando da CLI do .NET Core pode ser executado com dotnet watch . Por exemplo:
Executar dotnet watch run na pasta WebApp. O resultado do console indica que watch foi iniciado.
Salve o arquivo. O resultado do console indica que o dotnet watch detectou uma alteração de arquivo e reiniciou o
aplicativo.
Verifique se http://localhost:<port number>/api/math/product?a=4&b=5 retorna o resultado correto.
5. Corrija o código do método Product para que ele retorne o produto. Salve o arquivo.
dotnet watch detecta a alteração de arquivo e executa os testes novamente. O resultado do console indica a
aprovação nos testes.
Mais itens podem ser adicionados à lista de inspeção editando o arquivo .csproj. Os itens podem ser especificados
individualmente ou usando padrões glob.
<ItemGroup>
<!-- extends watching group to include *.js files -->
<Watch Include="**\*.js" Exclude="node_modules\**\*;**\*.js.map;obj\**\*;bin\**\*" />
</ItemGroup>
<ItemGroup>
<!-- exclude Generated.cs from dotnet-watch -->
<Compile Include="Generated.cs" Watch="false" />
<Project>
<ItemGroup>
<TestProjects Include="**\*.csproj" />
<Watch Include="**\*.cs" />
</ItemGroup>
<Target Name="Test">
<MSBuild Targets="VSTest" Projects="@(TestProjects)" />
</Target>
Para iniciar a observação de arquivo em ambos os projetos, mude para a pasta de teste. Execute o seguinte
comando:
dotnet watch msbuild /t:Test
dotnet-watch no GitHub
dotnet-watch faz parte do repositório aspnet/AspNetCore do GitHub.
Ativação de middleware baseada em alocador no
ASP.NET Core
30/10/2018 • 3 minutes 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 = "FactoryActivatedMiddleware",
Value = keyValue
});
await _db.SaveChangesAsync();
}
await next(context);
}
}
Não é possível passar objetos para o middleware ativado por alocador com UseMiddleware :
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb"));
services.AddTransient<FactoryActivatedMiddleware>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
app.UseConventionalMiddleware();
app.UseFactoryActivatedMiddleware();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
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.
Recursos adicionais
Middleware do ASP.NET Core
Ativação de middleware com um contêiner de terceiros no ASP.NET Core
Ativação de middleware com um contêiner de
terceiros no ASP.NET Core
30/10/2018 • 4 minutes to read • Edit Online
A implementação do middleware do exemplo registra o valor fornecido por um parâmetro de cadeia de consulta (
key ). O middleware usa um contexto de banco de dados injetado (um serviço com escopo) para registrar o valor
da cadeia de consulta em um banco de dados em memória.
NOTE
O aplicativo de exemplo usa o Simple Injector simplesmente para fins de demonstração. O uso do Simple Injector não é um
endosso. As abordagens de ativação do middleware descritas na documentação do Simple Injector e nos problemas do
GitHub são recomendadas pelos mantenedores do Simple Injector. Para obter mais informações, confira a Documentação do
Simple Injector e o repositório do GitHub do Simple Injector.
IMiddlewareFactory
IMiddlewareFactory fornece métodos para a criação do middleware.
No aplicativo de amostra, um alocador de middleware é implementado para criar uma instância de
SimpleInjectorActivatedMiddleware . O alocador de middleware usa o contêiner do Simple Injector para resolver o
middleware:
public class SimpleInjectorMiddlewareFactory : IMiddlewareFactory
{
private readonly Container _container;
IMiddleware
IMiddleware define o middleware para o pipeline de solicitação do aplicativo.
Middleware ativado por uma implementação de IMiddlewareFactory
(Middleware/SimpleInjectorActivatedMiddleware.cs):
if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "SimpleInjectorActivatedMiddleware",
Value = keyValue
});
await _db.SaveChangesAsync();
}
await next(context);
}
}
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
_container.Register<AppDbContext>(() =>
{
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseInMemoryDatabase("InMemoryDb");
return new AppDbContext(optionsBuilder.Options);
}, Lifestyle.Scoped);
_container.Register<SimpleInjectorActivatedMiddleware>();
_container.Verify();
}
app.UseSimpleInjectorActivatedMiddleware();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
Recursos adicionais
Middleware
Ativação de middleware de fábrica
Repositório do GitHub do Simple Injector
Documentação do Simple Injector
Migrar do ASP.NET Core 2.2 a 3.0 versão prévia 2
07/02/2019 • 4 minutes to read • Edit Online
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
Visualização do Visual Studio 2019 com o ASP.NET e desenvolvimento web carga de trabalho
.NET core SDK 3.0 versão prévia
<TargetFramework>netcoreapp3.0</TargetFramework>
Suporte de Json.NET
Como parte do trabalho para melhorar a estrutura compartilhada do ASP.NET Core, Json.NET foi removida da
estrutura compartilhada do ASP.NET Core.
Para usar o Json.NET em um projeto do ASP.NET Core 3.0:
Adicione uma referência de pacote ao Microsoft.AspNetCore.Mvc.NewtonsoftJson
Atualização ConfigureServices chamar AddNewtonsoftJson() .
services.AddMvc()
.AddNewtonsoftJson();
services.AddMvc()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver());
O código a seguir mostra o modelo gerado do ASP.NET Core 2.2 Program classe:
services.AddSignalR(...)
.AddJsonProtocol(...) // 2.2
services.AddSignalR(...)
.AddNewtonsoftJsonProtocol(...) // 3.0
Pré-requisitos
Visual Studio
Visual Studio Code
Visual Studio para Mac
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
SDK 2.2 ou posterior do .NET Core
<TargetFramework>netcoreapp2.2</TargetFramework>
Projetos direcionados ao .NET Framework podem continuar a usar o TFM de uma versão maior ou igual ao .NET
Framework 4.6.1:
<TargetFramework>net461</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
Não há suporte para o modelo de hospedagem em processo para aplicativos ASP.NET Core direcionados ao .NET
Framework.
Para obter mais informações, consulte Módulo do ASP.NET Core.
Para obter mais informações, consulte Metapacote Microsoft.AspNetCore.App para ASP.NET Core 2.1 e posterior.
A referência do metapacote deve ser semelhante à seguinte <PackageReference /> nó:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
Se o destino do .NET Framework, atualizar cada referência de pacote Version atributo 2.2.0 ou posterior. Aqui
estão as referências do pacote em um projeto ASP.NET Core 2.2 típico direcionado ao .NET Framework:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.CookiePolicy" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HttpsPolicy" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
</ItemGroup>
Detected package downgrade: Microsoft.AspNetCore.Razor.Design from 2.2.0 to 2.1.2. Reference the package
directly from the project to select a different version.
{
"sdk": {
"version": "2.2.100"
}
}
Se o aplicativo não chama CreateDefaultBuilder e cria o host manualmente na Program classe, chame UseKestrel
antes chamando ConfigureKestrel :
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.ConfigureKestrel((context, options) =>
{
// Set properties and call methods on options
})
.Build();
host.Run();
}
Para obter mais informações, consulte Implementação do servidor Web Kestrel no ASP.NET Core.
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
2.1 2.2
microsoft/dotnet:2.1-aspnetcore-runtime microsoft/dotnet:2.2-aspnetcore-runtime
microsoft/dotnet:2.1-sdk microsoft/dotnet:2.2-sdk
Alterar o FROM linhas no seu Dockerfile para usar as novas marcas de imagem na coluna de 2,2 da tabela anterior.
exemplo de 2.2:
Filtrando:
exemplo 1. x:
exemplo de 2.2:
public static void Main(string[] args)
{
var webHost = new WebHostBuilder()
// ...
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole()
.AddFilter<ConsoleLoggerProvider>
(category: null, level: LogLevel.Information)
// or
.AddFilter<ConsoleLoggerProvider>
((category, level) => category == "A" ||
level == LogLevel.Critical)
);
})
// ...
}
Carregamento da configuração:
exemplo 1. x:
exemplo de 2.2:
Recursos adicionais
Versão de compatibilidade do ASP.NET Core MVC
Metapacote Microsoft.AspNetCore.App para ASP.NET Core 2.1 e posterior
Referências de pacote implícitas
Migrar do ASP.NET Core 2.0 para 2.1
05/12/2018 • 18 minutes to read • Edit Online
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<UserSecretsId>aspnet-{Project Name}-{GUID}</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.3" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.4"
PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.3" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.2" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.4" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<UserSecretsId>aspnet-{Project Name}-{GUID}</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.1"
PrivateAssets="All" />
</ItemGroup>
</Project>
2.0 2.1
Alterar o FROM linhas no seu Dockerfile para usar as marcas e os novos nomes de imagem na coluna de 2.1 da
tabela anterior. Para obter mais informações, consulte Migrando do aspnetcore repositórios de docker para dotnet.
namespace WebApp1
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
O novo Main substitui a chamada para BuildWebHost com CreateWebHostBuilder. IWebHostBuilder foi
adicionado para dar suporte a um novo infraestrutura de teste de integração.
Alterações para a inicialização
O código a seguir mostra as alterações ao código de modelo 2.1 gerado. Todas as alterações são adicionadas
recentemente o código, exceto que UseBrowserLink foi removido:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace WebApp1
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
// If the app uses Session or TempData based on Session:
// app.UseSession();
app.UseMvc();
}
}
}
/ Account/Login Identidade/logon/conta
/ Conta/Logout Identidade/logoff/conta
/ Conta/gerenciar / Identidade/conta/gerenciar
Identidade de interface do usuário com a necessidade de biblioteca de identidade 2.1 para levar em conta as URLs
de identidade de aplicativos que tem o código usando a identidade e substitua 2.0 têm /Identity anexado para os
URIs de segmento. É uma maneira de lidar com os novos pontos de extremidade de identidade é configurar
redirecionamentos, por exemplo da /Account/Login para /Identity/Account/Login .
Atualizar identidade para a versão 2.1
As seguintes opções estão disponíveis para atualizar a identidade para o 2.1.
Use o código de identidade da interface do usuário 2.0 sem alterações. Usando o código de identidade da
interface do usuário 2.0 tem suporte total. Isso é uma boa abordagem quando alterações significativas foram
feitas para o código de identidade gerado.
Excluir seu código 2.0 de identidade existente e identidade de Scaffold em seu projeto. Seu projeto usará o
ASP.NET Core Identity biblioteca de classes Razor. Você pode gerar o código e a interface do usuário para
qualquer parte do código de identidade da interface do usuário que você modificou. Aplica as alterações de
código para o código gerado automaticamente recém-criado de interface do usuário.
Excluir seu código 2.0 de identidade existente e identidade de Scaffold em seu projeto com a opção de
substituir todos os arquivos.
Substitua a identidade 2.0 interface do usuário com a biblioteca de classes Razor identidade 2.1
Esta seção descreve as etapas para substituir o código de identidade do ASP.NET Core 2.0 modelo gerado com o
ASP.NET Core Identity biblioteca de classes Razor. As etapas a seguir são para um projeto de páginas do Razor,
mas a abordagem para um projeto MVC é semelhante.
Verifique se o arquivo de projeto é atualizado para usar 2.1 versões
Exclua as seguintes pastas e todos os arquivos contidos neles:
Controladores
Páginas/Account /
Extensões
Compile o projeto.
Criar o scaffolding de identidade em seu projeto:
Selecione os projetos saindo _layout. cshtml arquivo.
Selecione o + ícone no lado direito dos classe de contexto de dados. Aceite o nome padrão.
Selecione adicionar para criar uma nova classe de contexto de dados. Criar um novo contexto de dados
é necessária para criar o scaffolding. Você pode remover o novo contexto de dados na próxima seção.
Atualizar após o scaffolding de identidade
Excluir o scaffolder de identidade gerado IdentityDbContext derivado da classe na áreas/identidade/Data/
pasta.
Excluir Areas/Identity/IdentityHostingStartup.cs
Atualizar o _Loginpartial arquivo:
Mover páginas /_Loginpartial à páginas/Shared/_Loginpartial
Adicionar asp-area="Identity" os links de âncora e o formulário.
Atualização de <form /> elemento
<form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/Index", new {
area = "" })" method="post" id="logoutForm" class="navbar-right">
O código a seguir mostra o atualizada _Loginpartial arquivo:
@using Microsoft.AspNetCore.Identity
@if (SignInManager.IsSignedIn(User))
{
<form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/Index", new {
area = "" })" method="post" id="logoutForm" class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello
@UserManager.GetUserName(User)!</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="Identity" asp-page="/Account/Register">Register</a></li>
<li><a asp-area="Identity" asp-page="/Account/Login">Log in</a></li>
</ul>
}
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
Ver GDPR suporte no ASP.NET Core para obter informações sobre os arquivos anteriores.
Alterações no arquivo launchsettings. JSON
Como os aplicativos ASP.NET Core agora usam HTTPS por padrão, o Properties/launchSettings.json arquivo foi
alterado.
O JSON a seguir mostra o 2.0 anteriores modelo gerado launchsettings. JSON arquivo:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:1799/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebApp1": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:1798/"
}
}
}
O JSON a seguir mostra o novo 2.1 modelo gerado launchsettings. JSON arquivo:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:39191",
"sslPort": 44390
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebApp1": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Para obter mais informações, consulte Impor HTTPS no ASP.NET Core.
Alterações adicionais
Se hospedar o aplicativo no Windows com o IIS, instalar a versão mais recente pacote de hospedagem do .NET
Core.
SetCompatibilityVersion
Configuração de transporte
Migrar do ASP.NET Core 1.x para 2.0
26/10/2018 • 13 minutes to read • Edit Online
Pré-requisitos
Veja 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>
NOTE
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.9" />
</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 autenticação e identidade para o ASP.NET
Core 2.0
27/12/2018 • 13 minutes to read • Edit Online
Em projetos do 2.0, a autenticação é configurada por meio de serviços. Cada esquema de autenticação é
registrada na ConfigureServices método de Startup.cs. O UseIdentity o método é substituído por
UseAuthentication .
app.UseAuthentication();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
app.UseAuthentication();
app.UseAuthentication();
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 na Startup.cs:
Substitua os UseFacebookAuthentication chamada de método na 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 na Startup.cs:
Substitua os UseGoogleAuthentication chamada de método na 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 na Startup.cs:
Substitua os UseTwitterAuthentication chamada de método na Configure método com UseAuthentication
:
app.UseAuthentication();
services.AddAuthentication()
.AddTwitter(options =>
{
options.ConsumerKey = Configuration["auth:twitter:consumerkey"];
options.ConsumerSecret = Configuration["auth:twitter:consumersecret"];
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
Como alternativa, use uma versão sobrecarregada do AddAuthentication método para definir mais de uma
propriedade. No exemplo a seguir o método sobrecarregado, o esquema padrão é definido como
CookieAuthenticationDefaults.AuthenticationScheme . O esquema de autenticação como alternativa, pode ser
especificado dentro de seu indivíduo [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 do 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. Esse método adiciona cookies para você e define o padrão
autenticar e esquemas de desafio 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 de desafio do
trabalho.
Instâncias de IdentityCookieOptions
Um efeito colateral das 2.0 alterações é o comutador que usa o chamado opções em vez de instâncias de opções
de cookie. A capacidade de personalizar os nomes de esquema do 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>();
}
/// <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 impedir que as chaves estrangeiras duplicadas ao executar migrações do EF Core, adicione o seguinte ao seu
IdentityDbContext classe OnModelCreating método (depois que o base.OnModelCreating(); chamar ):
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Core Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Core Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Roles)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
Substituir GetExternalAuthenticationSchemes
O método síncrono GetExternalAuthenticationSchemes foi removido para uma versão assíncrona. projetos do 1.x
tem 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 projetos do 2.0, o tipo de retorno é alterado para IList<AuthenticationScheme> . Esse novo tipo de retorno
requer substituindo o Microsoft.AspNetCore.Http.Authentication importação de 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 mais detalhes e discussões, consulte o discussão do Auth 2.0 problema no GitHub.
Migrar do ASP.NET para o ASP.NET Core
13/12/2018 • 13 minutes to read • Edit Online
Pré-requisitos
SDK 2.2 ou posterior do .NET Core
Frameworks de destino
Projetos do ASP.NET Core 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. Instale o metapacote Microsoft.AspNetCore.App em seu projeto:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</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. Para saber mais, confira Metapacote Microsoft.AspNetCore.App para ASP.NET Core.
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)
{
CreateWebHostBuilder(args).Build().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), os métodos de extensão configuram o pipeline
com suporte para:
Páginas de erro
Segurança de Transporte Estrita de HTTP
Redirecionamento de HTTP para HTTPS
ASP.NET Core MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
O host e o aplicativo foram separados, o que fornece a flexibilidade de mover para uma plataforma diferente no
futuro.
NOTE
Para obter uma referência mais aprofundada sobre o Startup do ASP.NET Core e middleware, veja Startup no ASP.NET Core
Armazenar 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.
NOTE
Para uma referência mais detalhada sobre configuração do ASP.NET Core, veja 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.
NOTE
Para obter mais informações sobre injeção de dependência, confira Injeção de dependência.
Por exemplo, um ativo de imagem na pasta wwwroot/imagens está acessível para o navegador em um local como
http://<app>/images/<imageFileName> .
NOTE
Para obter uma referência mais aprofundada sobre como servir arquivos estáticos no ASP.NET Core, veja Arquivos estáticos.
Recursos adicionais
Fazendo a portabilidade de Bibliotecas para o .NET Core
Migrar do ASP.NET MVC para ASP.NET Core MVC
10/11/2018 • 15 minutes to read • Edit Online
NOTE
Os números de versão nos exemplos podem não ser atuais. Talvez você precise atualizar seus projetos de acordo.
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
Copie 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 interno do modelo é ActionResult; no
ASP.NET Core MVC, os retorno de métodos de ação IActionResult em vez disso. ActionResult
implementa IActionResult , portanto, não é necessário alterar o tipo de retorno de métodos de ação.
Cópia de About. cshtml, Contact. cshtml, e index. cshtml arquivos de exibição do Razor do projeto ASP.NET
MVC para o projeto ASP.NET Core.
Execute o aplicativo ASP.NET Core e cada método de teste. Ainda não migramos o arquivo de layout ou
estilos ainda, portanto, os modos de exibição renderizados contêm apenas o conteúdo nos arquivos de
exibição. Você não terá os links de arquivo gerado de layout para o About e Contact modos de exibição,
portanto, você precisará invocá-los a partir do navegador (substitua 4492 com o número da porta usado
em seu projeto).
http://localhost:4492/home/about
http://localhost:4492/home/contact
Conteúdo estático
Nas versões anteriores do ASP.NET MVC, o conteúdo estático foi hospedado da raiz do projeto da web e foi
Intercalado com arquivos do lado do servidor. No ASP.NET Core, conteúdo estático é hospedado na wwwroot
pasta. Você desejará copiar o conteúdo estático de seu aplicativo ASP.NET MVC antigo para o wwwroot pasta em
seu projeto ASP.NET Core. Nessa conversão de exemplo:
Cópia de /favicon.ico arquivo de projeto MVC antigo para o wwwroot pasta no projeto do ASP.NET Core.
O ASP.NET MVC antigo projeto usa Bootstrap para seu estilo e armazena a inicialização de arquivos no conteúdo
e Scripts pastas. O modelo, que gerou o antigo projeto do ASP.NET MVC, faz referência a inicialização no arquivo
de layout (Views/Shared/_Layout.cshtml). Você pode copiar o bootstrap. js e Bootstrap arquivos do ASP.NET MVC
de projeto para o wwwroot pasta no novo projeto. Em vez disso, vamos adicionar suporte para o Bootstrap (e
outras bibliotecas do lado do cliente) usando as CDNs na próxima seção.
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<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>
<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 utiliza auxiliares de marca e tem outras melhorias.
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?}");
});
}
}
}
ASP.NET Core converte as exceções sem tratamento em um aplicativo web em respostas de erro HTTP 500.
Normalmente, os detalhes do erro não estão incluídos nessas respostas para evitar a divulgação de informações
possivelmente confidenciais sobre o servidor. Ver usando a página de exceção do desenvolvedor na tratar
erros para obter mais informações.
Recursos adicionais
Desenvolvimento do lado do cliente
Auxiliares de marcação
Migrar da API Web ASP.NET para ASP.NET Core
11/12/2018 • 13 minutes to read • Edit Online
Pré-requisitos
Visual Studio 2017 versão 15.9 ou posterior com a carga de trabalho ASP.NET e desenvolvimento para a
Web
SDK 2.2 ou posterior do .NET Core
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 Web ASP.NET. Nesse caso, a API da Web do ASP.NET 4.x
espera que as URLs para corresponder ao formato /api/{controller}/{id} , com {id} opcionais.
O ProductsApp projeto inclui um controlador. O controlador herda de ApiController e contém duas ações:
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
}
};
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; }
}
}
As seções a seguir demonstram a migração do projeto API da Web para ASP.NET Core MVC.
Migrar configuração
ASP.NET Core não usa o App_Start pasta ou o global. asax arquivo e o Web. config arquivo for adicionado no
momento da publicação. Startup.CS é o substituto do global. asax e está localizado na raiz do projeto. O Startup
classe manipula todas as tarefas de inicialização do aplicativo. Para obter mais informações, consulte Inicialização
de aplicativo no ASP.NET Core.
No ASP.NET Core MVC, o roteamento de atributo é incluído por padrão quando UseMvc é chamado no
Startup.Configure . O seguinte UseMvc chamar substitui o ProductsApp do projeto app_start arquivo:
app.UseHttpsRedirection();
app.UseMvc();
}
return product;
Configurar o roteamento
Configure o roteamento da seguinte maneira:
1. Decore o ProductsController classe com os seguintes atributos:
[Route("api/[controller]")]
[ApiController]
A alteração anterior:
É necessário para usar o [ApiController] atributo no nível do controlador.
Aceita o potencialmente significativa comportamentos introduzidos no ASP.NET Core 2.2.
3. Habilitar as solicitações HTTP Get para o ProductController ações:
Aplicar a [HttpGet] atributo para o GetAllProducts ação.
Aplicar a [HttpGet("{id}")] de atributo para o GetProduct ação.
Após as alterações anteriores e a remoção do não utilizado using instruções ProductsController.cs arquivo tem
esta aparência:
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using ProductsCore.Models;
namespace ProductsCore.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
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
}
};
[HttpGet]
public IEnumerable<Product> GetAllProducts()
{
return products;
}
[HttpGet("{id}")]
public ActionResult<Product> GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return product;
}
}
}
Execute o projeto migrado e navegue até /api/products . É exibida uma lista completa dos três produtos. Navegue
para /api/products/1 . O primeiro produto será exibida.
Correção de compatibilidade
O Microsoft.AspNetCore.Mvc.WebApiCompatShim biblioteca fornece um shim de compatibilidade para mover
projetos de API da Web do ASP.NET 4.x ASP.NET Core. O shim de compatibilidade se estende do ASP.NET Core
para dar suporte a uma série de convenções da API de Web do ASP.NET 4. x 2. O exemplo portado anteriormente
neste documento é bastante básico que o shim de compatibilidade era desnecessário. Para projetos maiores, usem
o shim de compatibilidade pode ser útil para temporariamente preenchendo a lacuna de API entre o ASP.NET
Core e ASP.NET 4.x Web API 2.
O shim de compatibilidade de API da Web destina-se a ser usado como uma medida temporária para dar suporte
à migração grande API da Web projetos do ASP.NET 4.x ASP.NET Core. Ao longo do tempo, os projetos devem
ser atualizados para usar os padrões do ASP.NET Core em vez de usar o shim de compatibilidade.
Recursos de compatibilidade incluídos no Microsoft.AspNetCore.Mvc.WebApiCompatShim incluem:
Adiciona um ApiController tipo de forma que os tipos de base dos controladores não precisam ser
atualizados.
Habilita a associação de modelo de estilo de API da Web. Funções de associação de maneira semelhante do
ASP.NET de modelo do ASP.NET Core MVC 4. x MVC 5, por padrão. As alterações de correção de
compatibilidade associação de modelo para ser mais semelhante a convenções de associação de modelo do
ASP.NET Web API 2 de 4. x. Por exemplo, tipos complexos são vinculados automaticamente do corpo da
solicitação.
Estende a associação de modelo para que as ações do controlador podem usar parâmetros de tipo
HttpRequestMessage .
Adiciona os formatadores de mensagem, permitindo que as ações para retornar os resultados do tipo
HttpResponseMessage .
Adiciona métodos de resposta adicionais que ações de API Web 2 podem ter usado para servir as respostas:
HttpResponseMessage geradores de:
CreateResponse<T>
CreateErrorResponse
Métodos de ação de resultados:
BadRequestErrorMessageResult
ExceptionResult
InternalServerErrorResult
InvalidModelStateResult
NegotiatedContentResult
ResponseMessageResult
Adiciona uma instância do IContentNegotiator para o aplicativo disponibiliza os tipos de conteúdo relacionado
a negociação do e do contêiner de DI (injeção) de dependência Microsoft.AspNet.WebApi.Client. Exemplos de
tais tipos DefaultContentNegotiator e MediaTypeFormatter .
Recursos adicionais
Criar APIs Web com o ASP.NET Core
Tipos de retorno de ação do controlador na API Web ASP.NET Core
Versão de compatibilidade do ASP.NET Core MVC
Migrar a configuração para o ASP.NET Core
30/10/2018 • 4 minutes to read • Edit Online
Configuração da instalação
O ASP.NET Core não usa 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 dentro global. asax. Posteriormente, no ASP.NET MVC, uma Startup.cs arquivo foi incluído na raiz do
projeto; e ele foi chamado quando o aplicativo foi iniciado. ASP.NET Core tem adotado essa abordagem
completamente colocando toda lógica de inicialização na Startup.cs arquivo.
O Web. config arquivo também foi substituído no ASP.NET Core. Configuração em si agora pode ser configurada
como parte do procedimento de inicialização de aplicativo descrito em Startup.cs. Configuração ainda pode utilizar
arquivos XML, mas normalmente projetos do ASP.NET Core serão coloca valores de configuração em um arquivo
formatado em JSON, como appSettings. JSON. Sistema de configuração do ASP.NET Core também poderá
acessar facilmente as variáveis de ambiente, que podem fornecer um mais local seguro e robusto para valores
específicos do ambiente. Isso é especialmente verdadeiro para segredos, como cadeias de caracteres de conexão e
as chaves de API que não devem ser verificadas no controle de origem. Ver configuração para saber mais sobre a
configuração no ASP.NET Core.
Neste artigo, estamos começando com o projeto ASP.NET Core migrado parcialmente desde o artigo anterior. A
configuração da instalação, adicione o seguinte construtor e propriedade como o Startup.cs arquivo localizado na
raiz do projeto:
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 um appSettings. JSON arquivo na raiz do projeto usando o modelo de item apropriado:
Migrar definições da configuração da 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, no <connectionStrings> elemento. Em nosso projeto do ASP.NET Core, vamos armazenar essas
informações na 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 seu banco de
dados.
Resumo
ASP.NET Core coloca toda lógica de inicialização para o aplicativo em um único arquivo, em que 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 as variáveis de ambiente.
Migrar autenticação e identidade para o ASP.NET
Core
13/02/2019 • 5 minutes to read • Edit Online
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
Neste ponto, há dois tipos referenciados no código acima que nós ainda não tiver migrado do projeto ASP.NET
MVC: ApplicationDbContext e ApplicationUser . Criar um novo modelos pasta no ASP.NET Core de 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 usaremos um arquivo por classe no projeto migrado, já que é mais
clara.
ApplicationUser.cs:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
namespace NewMvcProject.Models
{
public class ApplicationUser : IdentityUser
{
}
}
ApplicationDbContext.cs:
using Microsoft.AspNetCore.Identity.EntityFramework;
using Microsoft.Data.Entity;
namespace NewMvcProject.Models
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
O projeto Web do ASP.NET Core MVC Starter não inclui muita 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 usuário do seu aplicativo e DbContext classes, bem como outras classes de modelo
utiliza o seu aplicativo. Por exemplo, se sua DbContext tem uma DbSet<Album> , você precisa para migrar o Album
classe.
Com esses arquivos em vigor, o Startup.cs arquivo pode ser feito para compilar atualizando seu using instruções:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Nosso aplicativo agora está pronto para dar suporte a serviços de autenticação e identidade. Ele precisa apenas
ter esses recursos expostos aos usuários.
Agora, adicione uma nova exibição do Razor chamada loginpartial para o Views/Shared pasta:
Atualização loginpartial com o código a seguir (substitua todo seu conteúdo):
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@if (SignInManager.IsSignedIn(User))
{
<form asp-area="" asp-controller="Account" asp-action="Logout" 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 out</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
<li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
</ul>
}
Neste ponto, você deve ser capaz de atualizar o site no seu navegador.
Resumo
ASP.NET Core apresenta alterações aos recursos de identidade do ASP.NET. Neste artigo, você viu como migrar
os recursos de gerenciamento de usuário e autenticação de identidade do ASP.NET para ASP.NET Core.
Migrar de ClaimsPrincipal. Current
23/08/2018 • 5 minutes to read • Edit Online
Em projetos do ASP.NET 4.x, era comum usar ClaimsPrincipal. Current recuperar atual autenticado a identidade do
usuário e as declarações. No ASP.NET Core, essa propriedade não está definida. Código dependia ele precisa ser
atualizado para obter a identidade do usuário autenticado atual por meio de um modo diferente.
O código do exemplo anterior define Thread.CurrentPrincipal e verifica seu valor antes e depois de aguardar uma
chamada assíncrona. Thread.CurrentPrincipal é específico para o thread no qual ele é definido e o método
provavelmente retomar a execução em um thread diferente após o await. Consequentemente,
Thread.CurrentPrincipal está presente quando ele é verificado pela primeira vez, mas é nulo após a chamada para
await Task.Yield() .
Obtendo a atual identidade do usuário da coleção de serviço de injeção de dependência do aplicativo é mais
testável, também, desde que as identidades de teste podem ser injetadas com facilidade.
ControllerBase.User. Controladores do MVC podem acessar o usuário atual autenticado com suas usuário
propriedade.
HttpContext. Componentes com acesso ao atual HttpContext (por exemplo, middleware) pode obter o
usuário atual ClaimsPrincipal partir HttpContext.
Passada pelo chamador. Bibliotecas sem acesso ao atual HttpContext muitas vezes são chamados de
controladores ou componentes de middleware e pode ter a atual identidade do usuário passada como um
argumento.
IHttpContextAccessor. O projeto que está sendo migrado para o ASP.NET Core pode ser muito grande
para passar facilmente a atual identidade do usuário para todos os locais necessários. Nesses casos,
IHttpContextAccessor pode ser usado como uma solução alternativa. IHttpContextAccessor é capaz de
acessar atual HttpContext (se houver). Uma solução de curto prazo para obter a identidade do usuário atual
no código que ainda não foi atualizado para trabalhar com a arquitetura de orientado a DI do ASP.NET
Core seria:
Tornar IHttpContextAccessor disponíveis no contêiner de injeção de dependência chamando
AddHttpContextAccessor em Startup.ConfigureServices .
Obtenha uma instância de IHttpContextAccessor durante a inicialização e armazená-lo em uma variável
estática. A instância é disponibilizada para o código que anteriormente estava recuperando o usuário
atual de uma propriedade estática.
Recuperar o usuário atual ClaimsPrincipal usando HttpContextAccessor.HttpContext?.User . Se esse
código é usado fora do contexto de uma solicitação HTTP, o HttpContext é nulo.
O final de opção, usando IHttpContextAccessor , infringir os princípios do ASP.NET Core (preferindo dependências
injetadas dependências estáticas). Planeje, eventualmente, remova a dependência estático IHttpContextAccessor
auxiliar. Pode ser uma ponte útil, no entanto, quando a migração de grandes aplicativos ASP.NET existentes que
estavam usando previamente ClaimsPrincipal.Current .
Migrar de autenticação de associação do ASP.NET
para a identidade do ASP.NET Core 2.0
11/01/2019 • 11 minutes to read • Edit Online
NOTE
Este documento fornece as etapas necessárias para migrar o esquema de banco de dados para aplicativos baseados em
associação do ASP.NET para o esquema de banco de dados usado para a identidade do ASP.NET Core. Para obter mais
informações sobre como migrar da autenticação baseada em associação do ASP.NET para ASP.NET Identity, consulte migrar
um aplicativo existente da associação do SQL para o ASP.NET Identity. Para obter mais informações sobre a identidade do
ASP.NET Core, consulte Introdução à identidade no ASP.NET Core.
Para migrar aplicativos existentes para a identidade do ASP.NET Core 2.0, os dados nessas tabelas precisam ser
migrados para as tabelas usadas pelo novo esquema de identidade.
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=aspnet-core-
identity;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
7. Selecione modo de exibição > SQL Server Object Explorer. Expanda o nó correspondente ao nome do
banco de dados especificado na ConnectionStrings:DefaultConnection propriedade de appSettings. JSON.
O Update-Database comando criou o banco de dados especificado com o esquema e os dados necessários
para a inicialização do aplicativo. A imagem a seguir ilustra a estrutura da tabela que é criada com as etapas
anteriores.
Migrar o esquema
Há diferenças sutis nos campos de associação e o ASP.NET Core Identity e estruturas de tabela. O padrão foi
alterado significativamente para a autenticação/autorização com aplicativos ASP.NET e ASP.NET Core. Os objetos
principais que ainda são utilizados com identidade são os usuários e funções. Aqui estão as tabelas de mapeamento
para os usuários, funções, e UserRoles.
Usuários
ASSOCIAÇÃO
IDENTIDADE (DBO.ASPNET_USERS /
(DBO. ASPNETUSERS) DBO.ASPNET_MEMBERSHIP)
NOTE
Nem todos os mapeamentos de campo se parecer com relações um para um dos membros no ASP.NET Core Identity. A
tabela anterior usa o esquema de usuário da associação padrão e mapeia-os para o esquema de identidade do ASP.NET Core.
Todos os outros campos personalizados que foram usados para associação precisam ser mapeados manualmente. Em que
esse mapeamento, não há nenhum mapa para senhas, como critérios de senha e a senha salts não migram entre os dois. É
recomendável deixar a senha como null e pedir que os usuários redefinam suas senhas. No ASP.NET Core Identity,
LockoutEnd deverá ser definida para alguma data no futuro, se o usuário está bloqueado. Isso é mostrado no script de
migração.
Funções
IDENTIDADE ASSOCIAÇÃO
(DBO. ASPNETROLES) (DBO.ASPNET_ROLES)
Funções de usuário
IDENTIDADE ASSOCIAÇÃO
(DBO. ASPNETUSERROLES) (DBO.ASPNET_USERSINROLES)
Fazer referência as tabelas de mapeamento anterior durante a criação de um script de migração para os usuários e
funções. O exemplo a seguir pressupõe que você tenha dois bancos de dados em um servidor de banco de dados.
Um banco de dados contém o esquema de associação do ASP.NET existente e os dados. A outra
CoreIdentitySample banco de dados foi criado usando as etapas descritas anteriormente. Os comentários são
incluídas embutidas para obter mais detalhes.
-- INSERT USERS
INSERT INTO CoreIdentitySample.dbo.AspNetUsers
(Id,
UserName,
NormalizedUserName,
PasswordHash,
SecurityStamp,
EmailConfirmed,
PhoneNumber,
PhoneNumberConfirmed,
TwoFactorEnabled,
LockoutEnd,
LockoutEnabled,
AccessFailedCount,
Email,
NormalizedEmail)
SELECT aspnet_Users.UserId,
aspnet_Users.UserName,
-- The NormalizedUserName value is upper case in ASP.NET Core Identity
UPPER(aspnet_Users.UserName),
-- Creates an empty password since passwords don't map between the 2 schemas
'',
/*
The SecurityStamp token is used to verify the state of an account and
is subject to change at any time. It should be initialized as a new ID.
*/
NewID(),
/*
EmailConfirmed is set when a new user is created and confirmed via email.
Users must have this set during migration to reset passwords.
*/
1,
aspnet_Users.MobileAlias,
CASE
WHEN aspnet_Users.MobileAlias IS NULL THEN 0
ELSE 1
END,
-- 2FA likely wasn't setup in Membership for users, so setting as false.
0,
CASE
-- Setting lockout date to time in the future (1,000 years)
WHEN aspnet_Membership.IsLockedOut = 1 THEN Dateadd(year, 1000,
Sysutcdatetime())
ELSE NULL
END,
END,
aspnet_Membership.IsLockedOut,
/*
AccessFailedAccount is used to track failed logins. This is stored in
Membership in multiple columns. Setting to 0 arbitrarily.
*/
0,
aspnet_Membership.Email,
-- The NormalizedEmail value is upper case in ASP.NET Core Identity
UPPER(aspnet_Membership.Email)
FROM aspnet_Users
LEFT OUTER JOIN aspnet_Membership
ON aspnet_Membership.ApplicationId =
aspnet_Users.ApplicationId
AND aspnet_Users.UserId = aspnet_Membership.UserId
LEFT OUTER JOIN CoreIdentitySample.dbo.AspNetUsers
ON aspnet_Membership.UserId = AspNetUsers.Id
WHERE AspNetUsers.Id IS NULL
-- INSERT ROLES
INSERT INTO CoreIdentitySample.dbo.AspNetRoles(Id, Name)
SELECT RoleId, RoleName
FROM aspnet_Roles;
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION MigrateUsersAndRoles
RETURN
END
Após a conclusão do script anterior, o aplicativo ASP.NET Core Identity criado anteriormente é preenchido com os
usuários de associação. Os usuários precisam alterar suas senhas antes de fazer logon.
NOTE
Se o sistema de associação tivesse os usuários com nomes de usuário que não correspondiam a seu endereço de email, as
alterações são necessárias para o aplicativo criado anteriormente para acomodar isso. O modelo padrão espera UserName e
Email sejam iguais. Para situações em que eles são diferentes, o processo de logon deve ser modificado para usar
UserName em vez de Email .
Os manipuladores são:
As classes que implementam IHttpHandler
Usado para manipular solicitações com um determinado nome de arquivo ou a extensão, como .report
Configurado em Web. config
Módulos são:
As classes que implementam IHttpModule
Chamado para cada solicitação
Capazes de curto-circuito (interromper o processamento adicional de uma solicitação)
Capaz de adicionar à resposta HTTP, ou criar seus próprios
Configurado em Web. config
A ordem na qual os módulos de processam solicitações de entrada é determinada por:
1. O ciclo de vida do aplicativo, que é disparado pelo ASP.NET um eventos da série: BeginRequest,
AuthenticateRequest, etc. Cada módulo pode criar um manipulador para um ou mais eventos.
2. Para o mesmo evento, a ordem na qual eles foram configurados na Web. config.
Além de módulos, você pode adicionar manipuladores para os eventos de ciclo de vida para seus 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 na Middleware página, um middleware do ASP.NET Core é uma classe que expõe um
Invoke levando método um HttpContext e retornando um Task . Seu novo middleware terá esta aparência:
// 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, não chamando Invoke no próximo middleware no pipeline. Tenha em mente que
isso não encerra completamente a solicitação, porque o middleware anterior ainda será chamado quando a
resposta faz seu caminho através 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 seu novo middleware, você pode achar que seu código não era
compilado porque o HttpContext classe foi alterado significativamente no ASP.NET Core. Mais tarde, você verá
como migrar para ASP.NET Core HttpContext novo.
Converter isso por adicionando seu novo middleware ao 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?}");
});
}
O ponto exato no pipeline de onde você insere seu middleware novo depende do evento que ele tratado como um
módulo ( BeginRequest , EndRequest , etc.) e sua ordem na sua lista de módulos Web. config.
Conforme anteriormente não mencionado, há mais nenhum ciclo de vida do aplicativo no ASP.NET Core e a
ordem na qual as respostas são processadas pelo middleware é diferente da ordem usada pelos módulos. Isso
pode tornar sua decisão de ordenação mais desafiador.
Se a 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);
}
// ...
Em seu projeto ASP.NET Core, isso pode ser traduzido para um middleware semelhante a esta:
// 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 ao middleware correspondente aos 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, portanto, não haverá nenhum próximo middleware para invocar.
Você pode converter isso 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
seria 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 seu manipulador HTTP.
Uma solução é ramificar o pipeline para solicitações com uma determinada extensão, usando o MapWhen método
de extensão. Você pode fazer isso no mesmo Configure método onde você pode adicionar o outro middleware:
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. Associar 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, adicione-o ao 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 passá-los para o middleware. O Use... método de extensão (o que
adiciona seu middleware ao pipeline) é um lugar 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 o middleware para utilize um parâmetro de opções. Forneça uma sobrecarga da Use... método
de extensão (que usa o parâmetro options e passa-o para UseMiddleware ). Quando UseMiddleware é
chamado com parâmetros, ele passa os parâmetros para o construtor de middleware quando ele instancia
o 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 ASP.NET Core. Esta seção mostra como converter as
propriedades mais usadas da System.Web.HttpContext para o novo Microsoft.AspNetCore.Http.HttpContext .
HttpContext
HttpContext. Items 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;
WARNING
Ler os valores de formulário apenas se for o tipo de conteúdo sub x-www-form-urlencoded ou dados de formulário.
string inputBody;
using (var reader = new System.IO.StreamReader(
httpContext.Request.Body, System.Text.Encoding.UTF8))
{
inputBody = reader.ReadToEnd();
}
WARNING
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 de tentativa de ler o
corpo após a primeira leitura lê um corpo vazio.
Isso não se aplica à leitura de um formulário, conforme 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
Atendendo a um arquivo é discutida aqui.
HttpContext.Response.Headers
Enviar cabeçalhos de resposta é complicado pelo fato de que se você defini-las depois que nada foi escrito para o
corpo da resposta, eles não funcionará.
A solução é definir um método de retorno de chamada que será chamado direita antes de gravar do início da
resposta. Isso é feito melhor no início do Invoke método em seu middleware. É esse 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, o envio de
cookies requer mesmo retorno de chamada usado 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 Logging 2.1 para 2.2 ou 3.0
08/01/2019 • 2 minutes to read • Edit Online
Este artigo descreve as etapas comuns para a migração de um aplicativo ASP.NET Core que usa
Microsoft.Extensions.Logging do 2.1, 2.2 ou 3.0.
// use loggerFactory
}
exemplo de 2.2:
2.1 a 3.0
No 3.0, use LoggingFactory.Create .
exemplo 2.1:
// use loggerFactory
}
exemplo 3.0:
Recursos adicionais
Registro em log no ASP.NET Core