Você está na página 1de 6245

Dê a sua opinião sobre a experiência de download do PDF.

Documentação do ASP.NET
Aprenda a usar o ASP.NET Core para criar aplicativos Web e serviços rápidos, seguros,
multiplataforma e baseados em nuvem. Procure tutoriais, código de exemplo, conceitos básicos,
referência de API e muito mais.

COMEÇAR AGORA VISÃO GERAL


Crie um aplicativo ASP.NET Core Visão geral do ASP.NET Core
em qualquer plataforma em 5
minutos

BAIXAR NOVIDADES
Baixar o .NET Novidades nos documentos do
ASP.NET Core

COMEÇAR AGORA COMEÇAR AGORA


Crie sua primeira interface do Crie sua primeira API Web
usuário da Web

COMEÇAR AGORA VISÃO GERAL


Crie seu primeiro aplicativo Web Documentação do ASP.NET 4.x
em tempo real

Desenvolver aplicativos ASP.NET Core


Escolha aplicativos Web interativos, APIs Web, aplicativos padronizados por MVC, aplicativos em tempo
real e muito mais

Aplicativos Blazor Aplicativos de API Interface do usuário da


interativos no lado do HTTP Web focada em
cliente Desenvolver serviços HTTP páginas com o Razor…
Desenvolva com componentes com ASP.NET Core Desenvolver aplicativos Web
de interface do usuário b Criar uma API Web mínima focados em páginas com uma
reutilizáveis que podem com ASP.NET Core separação clara de interesses
aproveitar o WebAssembly…
d Criar uma API Web com os b Crie seu primeiro aplicativo
e Visão geral controladores do ASP.NET Web do Razor Pages
Core
b Como criar seu primeiro g Crie uma Interface do
aplicativo Blazor g Gerar páginas de ajuda da usuário da Web focada em
API Web com o páginas que consome uma
b Crie seu primeiro aplicativo Swagger/OpenAPI
Blazor com componentes API Web
reutilizáveis p Tipos de retorno de ação p Sintaxe Razor
do controlador
p Modelos de hospedagem p Filtros
do Blazor p Formatar dados de
resposta p Roteamento
p Tratar erros p Aplicativos Web ASP.NET
Core acessíveis
g Chamar uma API Web do
ASP.NET Core com o
JavaScript

Interface do usuário da Aplicativos Web em Aplicativos de RPC


Web focada em tempo real com o (Chamada de
páginas com MVC SignalR Procedimento Remot…
Desenvolva aplicativos Web Adicione funcionalidade em Desenvolva serviços de alto
usando o padrão de design do tempo real ao seu aplicativo desempenho e contract-first
Model-View-Controller Web e habilite o código do com o gRPC no ASP.NET Core
lado do servidor para enviar…
e Visão geral e Visão geral
e Visão geral
b Crie seu primeiro aplicativo b Criar um cliente e servidor
ASP.NET Core MVC b Crie seu primeiro aplicativo gRPC
SignalR
p Exibições p Conceitos dos serviços
g SignalR com Blazor gRPC em C#
p Exibições parciais
WebAssembly
s Exemplos
p Controladores
g SignalR com TypeScript
p Ações de roteamento para p Comparar serviços gRPC
o controlador
s Exemplos com APIs HTTP
p Hubs g Adicionar um serviço gRPC
p Teste de unidade
a um aplicativo ASP.NET
p Recursos de cliente SignalR
Core
p Hospedar e dimensionar
g Chamar os serviços gRPC
com o cliente .NET
g Usar o gRPC em aplicativos
de navegador

Aplicativos Web Versões anteriores do Tutoriais em vídeo do


controlados por dados ASP.NET Framework ASP.NET Core
Criar aplicativos Web Explore as visões gerais,
q Série de vídeos básicos do
controlados por dados no tutoriais, conceitos
ASP.NET Core
ASP.NET Core fundamentais, arquitetura e

g SQL com o ASP.NET Core referência de API para versõe…
q Série de vídeos básicos do
Entity Framework Core
p Associação de dados no p ASP.NET 4.x com .NET Core e ASP.NET
ASP.NET Core Blazor
Core
g SQL Server Express e Razor
Pages
q Arquitetura de
microsserviço com o
g Entity Framework Core ASP.NET Core
com o Razor Pages
q Concentre-se na série de
g Entity Framework Core vídeos Blazor
com o ASP.NET Core MVC
q .NET Channel
g Armazenamento do Azure
g Armazenamento de Blobs
p Armazenamento de
Tabelas do Azure
p Cenários do Microsoft
Graph para ASP.NET Core

Conceitos e funcionalidades

Referência de API para ASP.NET Core Hospedar e implantar


Navegador da API .NET Visão geral
Implantar no Serviço de Aplicativo do Azure
DevOps para desenvolvedores ASP.NET principais
Linux com o Apache
Linux com o Nginx
Kestrel
IIS
Docker

Segurança e identidade Globalização e localização


Visão geral Visão geral
Autenticação Localização de objeto portátil
Autorização Extensibilidade de localização
Curso: Proteger um aplicativo Web ASP.NET Core Solução de problemas
com a estrutura de Identidade
Proteção de dados
Gerenciamento de segredos
Impor o HTTPS
Hospedar o Docker com HTTPS

Testar, depurar e solucionar problemas Azure e ASP.NET Core


Testes de unidades de páginas Razor Implantar um aplicativo Web do ASP.NET Core
Depuração remota ASP.NET Core e Docker
Depuração de instantâneo Hospedar um aplicativo Web com o Serviço de
Aplicativo do Azure
Testes de integração
Serviço de Aplicativo e Banco de Dados SQL do
Testes de estresse e carga
Azure
Solucionar problemas e depurar
Identidade gerenciada com o ASP.NET Core e o
Registrando em log Banco de Dados SQL do Azure
Teste de carga de aplicativos Web do Azure usando API Web com CORS no Serviço de Aplicativo do
o Azure DevOps Azure
Capturar logs de aplicativos Web com o log de
diagnóstico do Serviço de Aplicativo

Desempenho Recursos avançados


Visão geral Model binding
Memória e coleta de lixo Validação de modelo
Cache de resposta Gravar middleware
Compactação de resposta Operações de solicitação e de resposta
Ferramentas de diagnóstico Reescrita de URL
Testes de estresse e carga

Migração Arquitetura
ASP.NET Core 5.0 a 6.0 Escolher entre aplicativos Web tradicionais e SPAs
(aplicativos de página única)
ASP.NET Principais exemplos de código 5.0 para o
modelo de hospedagem mínimo 6.0 Princípios de arquitetura
ASP.NET Core 3.1 a 5.0 Arquiteturas comuns de aplicativo Web
ASP.NET Core 3.0 a 3.1 Tecnologias da Web comuns do lado do cliente
ASP.NET Core 2.2 a 3.0 Processo de desenvolvimento para o Azure
ASP.NET Core 2.1 a 2.2
ASP.NET Core 2.0 para 2.1
ASP.NET Core 1.x para 2.0
ASP.NET para ASP.NET Core
Contribua para a documentação do ASP.NET Core. Leia nosso guia do colaborador .
Documentação do ASP.NET Core – Quais
são as novidades?
Bem-vindo às novidades nos documentos do ASP.NET Core. Use esta página para
localizar rapidamente as alterações mais recentes.

Localizar atualizações de documentos do ASP.NET Core

h NOVIDADES

Junho de 2022

Março de 2022

Fevereiro de 2022

Janeiro de 2022

Dezembro de 2021

Novembro de 2021

Participe – contribua para a documentação do ASP.NET Core

e VISÃO GERAL

Repositório de documentos do ASP.NET Core

Estrutura e rótulos do projeto para problemas e solicitações de pull

p CONCEITO

Guia do colaborador do Microsoft Docs

Guia do colaborador do ASP.NET Core

Guia do colaborador de documentos de referência da API do ASP.NET Core

Comunidade

h NOVIDADES

Comunidade
Comunidade

Páginas de novidades relacionadas

h NOVIDADES

Atualizações de documentos do Xamarin

Notas sobre a versão do .NET Core

Notas sobre a versão do ASP.NET Core

Notas sobre a versão do compilador C# (Roslyn)

Notas sobre a versão do Visual Studio

Notas sobre a versão do Visual Studio para Mac

Notas sobre a versão do Visual Studio Code


Visão geral do ASP.NET Core
Artigo • 28/11/2022 • 11 minutos para o fim da leitura

Por Daniel Roth , Rick Anderson e Shaun Luttin

ASP.NET Core é uma estrutura de software livre multiplataforma, de alto desempenho e


de software livre para a criação de aplicativos modernos habilitados para nuvem e
conectados à Internet.

Com o ASP.NET Core, você pode:

Crie aplicativos e serviços Web, aplicativos de Internet das Coisas (IoT) e back-
ends móveis.
Usar suas ferramentas de desenvolvimento favoritas no Windows, macOS e Linux.
Implantar na nuvem ou local.
Execute no .NET Core.

Por que escolher o ASP.NET Core?


Milhões de desenvolvedores usam ou usaram ASP.NET 4.x para criar aplicativos Web.
ASP.NET Core é uma reformulação do ASP.NET 4.x, incluindo alterações arquitetônicas
que resultam em uma estrutura mais enxuta e modular.

O ASP.NET Core oferece os seguintes benefícios:

Uma história unificada para a criação da interface do usuário da Web e das APIs
Web.
Projetado para capacidade de teste.
Razor As páginas tornam os cenários focados em página de codificação mais fáceis
e produtivos.
Blazor permite que você use C# no navegador ao lado de JavaScript. Compartilhe
a lógica de aplicativo do lado do cliente e do servidor toda escrita com o .NET.
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.
Suporte para hospedagem de serviços RPC (chamada de procedimento remoto)
usando gRPC.
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 .
Capacidade de hospedar no seguinte:
Kestrel
IIS
HTTP.sys
Nginx
Apache
Docker
Controle de versão lado a lado.
Ferramentas que simplificam o moderno desenvolvimento para a Web.

Compilar APIs Web e uma interface do usuário


da Web usando o ASP.NET Core MVC
O ASP.NET Core MVC fornece recursos que ajudam você a compilar APIs Web e
aplicativos Web:

O padrão MVC (Model-View-Controller) ajuda a tornar as APIs Web e os


aplicativos Web testáveis.
Razor Pages é 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.
Razor A marcação fornece uma sintaxe produtiva para Razor exibições de Páginas
e MVC.
Os Auxiliares de Marcação permitem que o código do servidor participe da criação
e renderização de elementos HTML em arquivos do Razor.
O suporte interno para vários formatos de dados e negociação de conteúdo
permite que as APIs Web alcancem uma ampla gama de clientes, incluindo
navegadores e dispositivos móveis.
O model binding mapeia automaticamente os dados de solicitações HTTP para os
parâmetros de método de ação.
A Validação de Modelos executa automaticamente a validação no lado do cliente e
do servidor.

Desenvolvimento do lado do cliente


ASP.NET Core se integra perfeitamente a estruturas e bibliotecas populares do lado do
cliente, incluindo Blazor, Angular, React e Bootstrap . Para obter mais informações,
consulte ASP.NET Core Blazor e tópicos relacionados em Desenvolvimento do lado do
cliente.
estruturas de destino ASP.NET Core
ASP.NET Core 3.x ou posterior só pode ser direcionado ao .NET Core. Geralmente,
ASP.NET Core é composto por bibliotecas .NET Standard. As bibliotecas gravadas com
.NET Standard 2.0 podem ser executadas em qualquer plataforma .NET que implemente
o .NET Standard 2.0.

Há várias vantagens em direcionar para o .NET Core, e essas vantagens aumentam com
cada versão. Algumas vantagens do .NET Core em relação ao .NET Framework incluem:

Multiplataforma. É executado no Windows, macOS e Linux.


desempenho aprimorado
Controle de versão lado a lado
Novas APIs
Software livre

Caminho de aprendizado recomendado


Recomendamos a seguinte sequência de tutoriais para uma introdução ao
desenvolvimento de aplicativos ASP.NET Core:

1. Siga um tutorial para o tipo de aplicativo que você deseja desenvolver ou manter.

Tipo de aplicativo Cenário Tutorial

Aplicativo Web Novo desenvolvimento de interface do Introdução ao


usuário da Web do lado do servidor Razor Pages

Aplicativo Web Mantendo um aplicativo MVC Introdução ao


MVC

Aplicativo Web Desenvolvimento da interface do usuário Introdução ao


da Web do lado do cliente Blazor

API Web RESTserviços HTTP ful Criar uma API


Web†

Aplicativo de Chamada de Serviços de primeiro contrato usando Introdução a


Procedimento Remoto buffers de protocolo um serviço
gRPC

Aplicativo em tempo real Comunicação bidirecional entre servidores Introdução ao


e clientes conectados SignalR

2. Siga um tutorial que mostra como fazer o acesso básico a dados.


Cenário Tutorial

Novo desenvolvimento Razor Páginas com o Entity Framework Core

Mantendo um aplicativo MVC MVC com o Entity Framework Core

3. Leia uma visão geral dos conceitos básicos ASP.NET Core que se aplicam a todos
os tipos de aplicativo.

4. Procure no sumário outros tópicos de interesse.

†Tere também é um tutorial interativo da API Web. Nenhuma instalação local de


ferramentas de desenvolvimento é necessária. O código é executado em um Cloud Shell
do Azure no navegador e o curl é usado para teste.

Migrar do .NET Framework


Para obter um guia de referência para migrar aplicativos ASP.NET 4.x para ASP.NET
Core, consulte Migrar de ASP.NET para ASP.NET Core.

Como baixar uma amostra


Muitos dos artigos e tutoriais incluem links para exemplos de código.

1. Baixe o arquivo zip do repositório ASP.NET .


2. Descompacte o arquivo AspNetCore.Docs-main.zip .
3. Para acessar o aplicativo de exemplo de um artigo no repositório descompactado,
use a URL no link de exemplo do artigo para ajudá-lo a navegar até a pasta do
exemplo. Normalmente, o link de exemplo de um artigo aparece na parte superior
do artigo com o texto do link Exibir ou baixar o código de exemplo.

Diretivas do pré-processador no código de exemplo


Para demonstrar vários cenários, os aplicativos de exemplo usam as #define diretivas de
pré-processador e #if-#else/#elif-#endif para compilar e executar seletivamente
diferentes seções de código de exemplo. Para os exemplos que usam essa abordagem,
defina a #define diretiva na parte superior dos arquivos C# para definir o símbolo
associado ao cenário que você deseja executar. Alguns exemplos exigem a definição do
símbolo na parte superior de vários arquivos para executar um cenário.

Por exemplo, a seguinte lista de símbolo #define indica que quatro cenários estão
disponíveis (um cenário por símbolo). A configuração da amostra atual executa o
cenário TemplateCode :

C#

#define TemplateCode // or LogFromMain or ExpandDefault or FilterInCode

Para alterar a amostra que executará o cenário ExpandDefault , defina o símbolo


ExpandDefault e deixe os símbolos restantes comentados de fora:

C#

#define ExpandDefault // TemplateCode or LogFromMain or FilterInCode

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 por diretivas #region
e #endregion C#. O sistema de build de documentação injeta essas regiões nos tópicos
renderizados da documentação.

Os nomes de região geralmente contêm a palavra "snippet". O exemplo a seguir mostra


uma região chamada snippet_WebHostDefaults :

C#

#region snippet_WebHostDefaults
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
#endregion

O snippet de código C# precedente é referenciado no arquivo de markdown do tópico


com a seguinte linha:

Markdown

[!code-csharp[](sample/SampleApp/Program.cs?name=snippet_WebHostDefaults)]
Você pode ignorar com segurança (ou remover) as #region diretivas e #endregion que
cercam o código. Não altere o código dentro dessas diretivas 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 .

Alterações interruptivas e avisos de segurança


Alterações interruptivas e avisos de segurança são relatados no repositório
Comunicados . Os anúncios podem ser limitados a uma versão específica selecionando
um filtro rótulo .

Próximas etapas
Para saber mais, consulte os recursos a seguir:

Introdução ao ASP.NET Core


Publicar um aplicativo ASP.NET Core no Azure com o Visual Studio
conceitos básicos 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
Artigo • 10/01/2023 • 2 minutos para o fim da leitura

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.
Razor As páginas tornam os cenários focados em página de codificação mais fáceis
e produtivos.
Blazor permite que você use C# no navegador ao lado de JavaScript. Compartilhe
a lógica de aplicativo do lado do cliente e do servidor toda escrita com o .NET.
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.
Suporte para hospedagem de serviços RPC (chamada de procedimento remoto)
usando gRPC.
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 .
Capacidade de hospedar no seguinte:
Kestrel
IIS
HTTP.sys
Nginx
Apache
Docker
Controle de versão lado a lado.
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.

ASP.NET Core ASP.NET 4.x

Build para Windows, macOS ou Linux Build para Windows

Razor Pages é a abordagem recomendada para criar uma Usar Web Forms, SignalR,
interface do usuário da Web a partir do ASP.NET Core 2.x. MVC, API Web, WebHooks
Consulte também MVC, API Web e SignalR. ou Páginas da Web

Várias versões por computador Uma versão por computador

Desenvolva com o Visual Studio , Visual Studio para Mac ou Desenvolver com o Visual
Visual Studio Code usando C# ou F# Studio usando C#, VB ou F
#

Desempenho superior ao do ASP.NET 4.x Bom desempenho

Usar o runtime do .NET Core Use o runtime do .NET


Framework

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.

Cenários do ASP.NET Core


Sites
APIs
Em tempo real
Implantar um aplicativo do ASP.NET Core no Azure

Cenários do ASP.NET 4.x


Sites
APIs
Em tempo real
Criar um aplicativo Web ASP.NET 4.x no Azure

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
.NET 5 vs. .NET Framework para
aplicativos de servidor
Artigo • 28/11/2022 • 6 minutos para o fim da leitura

Há duas implementações do .NET com suporte para a criação de aplicativos do lado do


servidor.

Implementação Versões incluídas

.NET .NET Core 1.0 – 3.1, .NET 5 e versões posteriores do .NET.

.NET Framework .NET Framework 1.0 - 4.8

Ambas compartilham muitos dos mesmos componentes, e você pode compartilhar


código entre as duas. No entanto, há diferenças fundamentais entre os dois e sua
escolha depende do que você deseja realizar. Este artigo diretrizes sobre quando usar
cada um.

Use o .NET Core no seu aplicativo de servidor se:

Você tiver necessidades de plataforma cruzada.


Você estiver direcionando microsserviços.
Você estiver usando contêineres do Docker.
Você precisar de alto desempenho e sistemas escalonáveis.
Você precisar de versões do .NET correspondentes a cada aplicativo.

Use o .NET Framework para o aplicativo para servidores se:

Seu aplicativo usar o .NET Framework atualmente (a recomendação é estender em


vez de migrar).
Seu aplicativo usar bibliotecas de terceiros ou pacotes NuGet não disponíveis para
o .NET.
Seu aplicativo usar tecnologias .NET Framework que não estão disponíveis para o
.NET.
Seu aplicativo usar uma plataforma que não oferece suporte ao .NET.

Quando escolher o .NET


As seguintes seções oferecem uma explicação mais detalhada sobre os motivos
mencionados anteriormente para escolher o .NET em vez do .NET Framework.
Necessidades de plataforma cruzada
Se o aplicativo Web ou aplicativo de serviço precisar ser executado em várias
plataformas, por exemplo, Windows, Linux e macOS, use o .NET.

O .NET dá suporte aos sistemas operacionais mencionados anteriormente como sua


estação de trabalho de desenvolvimento. O Visual Studio fornece um IDE (ambiente de
desenvolvimento integrado) para Windows e macOS. Você também pode usar o Visual
Studio Code, que é executado no Windows, Linux e macOS. O Visual Studio Code dá
suporte ao .NET, incluindo IntelliSense e depuração. A maioria dos editores de terceiros,
como Sublime, Emacs e VI, trabalham com o .NET. Esses editores de terceiros obtém o
IntelliSense do editor usando o Omnisharp . Você também pode evitar qualquer editor
de código e usar diretamente a CLI do .NET, que está disponível para todas as
plataformas com suporte.

Arquitetura de microsserviços
Uma arquitetura de microsserviços possibilita uma combinação de tecnologias em um
limite de serviço. Essa combinação de tecnologias permite uma adoção gradual do .NET
para novos microsserviços que funcionam com outros serviços ou microsserviços. Por
exemplo, você pode combinar microsserviços ou serviços desenvolvidos com .NET
Framework, Java, Ruby ou outras tecnologias monolíticas.

Há muitas plataformas de infraestrutura disponíveis. O Azure Service Fabric é criado


para sistemas de microsserviço grandes e complexos. O Serviço de Aplicativo do
Azure é uma boa escolha para microsserviços sem monitoração de estado.
Alternativas de microsserviços com base no Docker se encaixam em qualquer
abordagem de microsserviços, conforme explicado na seção Contêineres . Todas essas
plataformas oferecem suporte ao .NET, e são ideais para hospedar microsserviços.

Para mais informações sobre arquitetura de microsserviços, consulte Microsserviços


.NET. Arquitetura para aplicativos .NET em contêineres.

Contêineres
Os contêineres são comumente usados com uma arquitetura de microsserviços. Os
contêineres também podem ser usados para colocar em contêiner os aplicativos ou
serviços Web que seguem qualquer padrão de arquitetura. .NET Framework podem ser
usados em contêineres do Windows. Ainda assim, a modularidade e a natureza leve do
.NET o tornam uma opção melhor para contêineres. Quando você está criando e
implantando um contêiner, o tamanho de sua imagem é muito menor com o .NET do
que com .NET Framework. Como ele é multiplataforma, você pode implantar aplicativos
de servidor em contêineres do Docker do Linux.

Os contêineres do Docker podem ser hospedados em sua própria infraestrutura do


Linux ou do Windows ou em um serviço de nuvem, como Serviço de Kubernetes do
Azure . O Serviço de Kubernetes do Azure pode gerenciar, orquestrar e dimensionar
aplicativos baseados em contêiner na nuvem.

Alto desempenho e sistemas escalonáveis


Quando o seu sistema precisa do melhor desempenho e escalabilidade possíveis, o .NET
e o ASP.NET Core são as melhores opções. O runtime do servidor de alto desempenho
para Windows Server e Linux torna ASP.NET Core uma estrutura Web de melhor
desempenho em benchmarks do TechEmpower .

O desempenho e a escalabilidade são especialmente relevantes para arquiteturas de


microsserviços, em que centenas de microsserviços podem estar em execução. Com o
ASP.NET Core, os sistemas são executados com um número bem menor de
servidores/VMs (Máquinas Virtuais). Os servidores/VMs reduzidos economizam custos
em infraestrutura e hospedagem.

Versões do .NET lado a lado por nível de aplicativo


Para instalar aplicativos com dependências em diferentes versões do .NET, é
recomendável o .NET. Essa implementação dá suporte à instalação lado a lado de
diferentes versões do runtime do .NET no mesmo computador. A instalação lado a lado
permite vários serviços no mesmo servidor, cada um em sua própria versão do .NET. Ela
também reduz os riscos e gera economia financeira nas operações de TI e atualizações
de aplicativo.

A instalação lado a lado não é possível com o .NET Framework. Ele é um componente
do Windows, e apenas uma versão pode existir em um computador por vez. Cada
versão do .NET Framework substitui a versão anterior. Se você instalar um novo
aplicativo direcionado a uma versão posterior do .NET Framework, poderá interromper
os aplicativos existentes executados no computador porque a versão anterior foi
substituída.

Quando escolher o .NET Framework


O .NET oferece benefícios significativos para novos aplicativos e padrões de aplicativo.
No entanto, o .NET Framework continua sendo a escolha natural para muitos cenários
existentes e, portanto, não é substituído pelo .NET em todos os aplicativos para
servidores.

Aplicativos .NET Framework atuais


Na maioria dos casos, não é necessário migrar aplicativos existentes para o .NET. Em vez
disso, recomendamos usar o .NET à medida que você estende um aplicativo existente,
como escrever um novo serviço Web em ASP.NET Core.

Bibliotecas de terceiros ou pacotes NuGet não disponíveis


para o .NET
O .NET Standard permite o compartilhamento de código entre todas as implementações
do .NET, incluindo o .NET Core/5+. Com o .NET Standard 2.0, um modo de
compatibilidade permite que os projetos do .NET Standard e do .NET referenciem
bibliotecas do .NET Framework. Para obter mais informações, consulte Suporte para
bibliotecas do .NET Framework.

Portanto, apenas nos casos em que as bibliotecas ou pacotes NuGet usarem tecnologias
que não estão disponíveis no .NET Standard ou .NET você precisará usar o .NET
Framework.

Tecnologias do .NET Framework não disponíveis para o


.NET
Algumas tecnologias do .NET Framework não estão disponíveis no .NET. A lista a seguir
mostra as tecnologias mais comuns não encontradas no .NET:

ASP.NET Web Forms aplicativos: ASP.NET Web Forms só estão disponíveis no .NET
Framework. ASP.NET Core não pode ser usado para ASP.NET Web Forms.

Páginas da Web do ASP.NET aplicativos: Páginas da Web do ASP.NET não estão


incluídos no ASP.NET Core.

Serviços relacionados ao fluxo de trabalho: Windows Workflow Foundation (WF),


Workflow Services (WCF + WF em um único serviço) e WCF Data Services
(anteriormente conhecido como "ADO.NET Data Services") só estão disponíveis em
.NET Framework.

Suporte à linguagem: No momento, há suporte para Visual Basic e F# no .NET,


mas não para todos os tipos de projeto. Para obter uma lista de modelos de
projeto com suporte, consulte Opções de modelo para o dotnet new.
Para obter mais informações, confira Tecnologias do .NET Framework não disponíveis no
.NET.

A plataforma não dá suporte ao .NET


Algumas plataformas de terceiros ou da Microsoft não oferecem suporte ao .NET.
Alguns serviços do Azure fornecem um SDK que ainda não está disponível para ser
consumido no .NET. Nesses casos, você pode usar a API REST equivalente em vez do
SDK do cliente.

Confira também
Escolher entre o ASP.NET e o ASP.NET Core
ASP.NET Core direcionado para o .NET Framework
Estruturas de destino
Introdução ao .NET
Portabilidade do .NET Framework para .NET 5
Introdução ao .NET e ao Docker
Implementações do .NET
Microsserviços do .NET. Arquitetura de aplicativos .NET em contêineres
Tutorial: introdução ao ASP.NET Core
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Este tutorial mostra como criar e executar um aplicativo Web ASP.NET Core usando a
CLI do .NET Core.

Você aprenderá a:

" Criar um projeto de aplicativo Web.


" Confiar no certificado de desenvolvimento.
" 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 6.0

Criar um projeto do aplicativo Web


Abra um shell de comando e insira o seguinte comando:

CLI do .NET
dotnet new webapp -o aspnetcoreapp

O comando anterior:

Cria um novo aplicativo Web.


O parâmetro -o aspnetcoreapp cria um diretório chamado aspnetcoreapp com os
arquivos de origem do aplicativo.

Confiar no certificado de desenvolvimento


Confie no certificado de desenvolvimento HTTPS:

Windows

CLI do .NET

dotnet dev-certs https --trust

O comando anterior exibe a caixa de diálogo a seguir:

Selecione Sim se você concordar com confiar no certificado de desenvolvimento.

Para obter mais informações, confira Confiar no certificado de desenvolvimento HTTPS


do ASP.NET Core
Executar o aplicativo
Execute os seguintes comandos:

CLI do .NET

cd aspnetcoreapp
dotnet watch run

Depois que o shell de comando indicar que o aplicativo foi iniciado, navegue até
https://localhost:{port} , onde {port} está a porta aleatória usada.

Editar uma página do Razor


Abra Pages/Index.cshtml e modifique e salve a página com a seguinte marcação
realçada:

CSHTML

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

Navegue até https://localhost:{port} , atualize a página e verifique se as alterações


são exibidas.

Próximas etapas
Neste tutorial, você aprendeu a:

" Criar um projeto de aplicativo Web.


" Confiar no certificado de desenvolvimento.
" Execute o projeto.
" Faça uma alteração.

Para saber mais sobre o ASP.NET Core, confira o seguinte:


Visão geral do ASP.NET Core
Novidades no ASP.NET Core 7.0
Artigo • 14/12/2022 • 29 minutos para o fim da leitura

Este artigo destaca as alterações mais significativas no ASP.NET Core 7.0 com links para
a documentação relevante.

Middleware de limitação de taxa no ASP.NET


Core
O Microsoft.AspNetCore.RateLimiting middleware fornece middleware de limitação de
taxa. Os aplicativos configuram políticas de limitação de taxa e anexam as políticas aos
pontos de extremidade. Para obter mais informações, consulte Middleware de limitação
de taxa em ASP.NET Core.

A autenticação usa um único esquema como


DefaultScheme
Como parte do trabalho para simplificar a autenticação, quando há apenas um único
esquema de autenticação registrado, ele é usado automaticamente como e
DefaultScheme não precisa ser especificado. Para obter mais informações, consulte
DefaultScheme.

MVC e Razor páginas

Suporte para modelos anuláveis em exibições e Razor


páginas do MVC
Há suporte para modelos de exibição ou página anuláveis para melhorar a experiência
ao usar a verificação de estado nulo com aplicativos ASP.NET Core:

C#

@model Product?

Associar com IParsable<T>.TryParse em controladores de


API e MVC
A IParsable<TSelf>.TryParse API dá suporte a valores de parâmetro de ação do
controlador de associação. Para obter mais informações, consulte Associar com
IParsable<T>.TryParse.

Personalizar o valor de cookie consentimento


Em ASP.NET Core versões anteriores a 7, a cookie validação de consentimento usa o
valor yes para indicar o cookie consentimento. Agora você pode especificar o valor que
representa o consentimento. Por exemplo, você pode usar true em vez de yes :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
options.ConsentCookieValue = "true";
});

var app = builder.Build();

Para obter mais informações, consulte Personalizar o valor de cookie consentimento.

Controladores de API

Associação de parâmetros com DI em controladores de


API
A associação de parâmetros para ações do controlador de API associa parâmetros por
meio de injeção de dependência quando o tipo é configurado como um serviço. Isso
significa que não é mais necessário aplicar explicitamente o [FromServices] atributo a
um parâmetro. No código a seguir, ambas as ações retornam a hora:

C#

[Route("[controller]")]
[ApiController]
public class MyController : ControllerBase
{
public ActionResult GetWithAttribute([FromServices] IDateTime dateTime)
=> Ok(dateTime.Now);
[Route("noAttribute")]
public ActionResult Get(IDateTime dateTime) => Ok(dateTime.Now);
}

Em casos raros, a DI automática pode interromper aplicativos que têm um tipo na DI


que também é aceito em um método de ação de controladores de API. Não é comum
ter um tipo na DI e como argumento em uma ação do controlador da API. Para
desabilitar a associação automática de parâmetros, defina
DisableImplicitFromServicesParameters

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
});

var app = builder.Build();

app.MapControllers();

app.Run();

No ASP.NET Core 7.0, os tipos na DI são verificados na inicialização do aplicativo com


IServiceProviderIsService para determinar se um argumento em uma ação do
controlador de API vem da DI ou de outras fontes.

O novo mecanismo para inferir a origem da associação dos parâmetros de ação do


Controlador de API usa as seguintes regras:

1. Um BindingInfo.BindingSource especificado anteriormente nunca é substituído.


2. Um parâmetro de tipo complexo, registrado no contêiner DI, é atribuído
BindingSource.Services.
3. Um parâmetro de tipo complexo, não é registrado no contêiner DI, é atribuído
BindingSource.Body.
4. Um parâmetro com um nome que aparece como um valor de rota em qualquer
modelo de rota é atribuído BindingSource.Path.
5. Todos os outros parâmetros são BindingSource.Query.
JSNomes de propriedade ON em erros de validação
Por padrão, quando ocorre um erro de validação, a validação do modelo produz um
ModelStateDictionary com o nome da propriedade como a chave de erro. Alguns
aplicativos, como aplicativos de página única, se beneficiam do uso JSde nomes de
propriedade ON para erros de validação gerados a partir de APIs Web. O código a
seguir configura a validação para usar o SystemTextJsonValidationMetadataProvider
para usar JSnomes de propriedade ON:

C#

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new
SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

O código a seguir configura a validação para usar o para usar JSo


NewtonsoftJsonValidationMetadataProvider nome da propriedade ON ao usar
Json.NET :

C#

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new
NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseAuthorization();

app.MapControllers();

app.Run();

Para obter mais informações, consulte Usar JSnomes de propriedade ON em erros de


validação

APIs mínimas

Filtros em aplicativos de API mínima


Filtros mínimos de API permitem que os desenvolvedores implementem a lógica de
negócios que dá suporte a:

Executando o código antes e depois do manipulador de rotas.


Inspecionar e modificar parâmetros fornecidos durante uma invocação do
manipulador de rotas.
Interceptando o comportamento de resposta de um manipulador de rotas.

Os filtros podem ser úteis nos seguintes cenários:

Validando os parâmetros de solicitação e o corpo que são enviados para um ponto


de extremidade.
Registrar em log informações sobre a solicitação e a resposta.
Validar se uma solicitação tem como destino uma versão de API com suporte.

Para obter mais informações, consulte Filtros em aplicativos de API mínimos

Associar matrizes e valores de cadeia de caracteres de


cabeçalhos e cadeias de caracteres de consulta
No ASP.NET 7, há suporte para associar cadeias de caracteres de consulta a uma matriz
de tipos primitivos, matrizes de cadeia de caracteres e StringValues :

C#

// Bind query string values to a primitive type array.


// GET /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
$"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");
// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

Há suporte para associar cadeias de caracteres de consulta ou valores de cabeçalho a


uma matriz de tipos complexos quando o tipo é TryParse implementado. Para obter
mais informações, consulte Associar matrizes e valores de cadeia de caracteres de
cabeçalhos e cadeias de caracteres de consulta.

Para obter mais informações, consulte Adicionar resumo ou descrição do ponto de


extremidade.

Associar o corpo da solicitação como um Stream ou


PipeReader

O corpo da solicitação pode ser associado como um Stream ou PipeReader para dar
suporte eficiente a cenários em que o usuário precisa processar dados e:

Armazene os dados no armazenamento de blobs ou enfileira os dados para um


provedor de filas.
Processe os dados armazenados com um processo de trabalho ou uma função de
nuvem.

Por exemplo, os dados podem ser enfileirados no Armazenamento de Filas do Azure ou


armazenados no Armazenamento de Blobs do Azure.

Para obter mais informações, consulte Associar o corpo da solicitação como um Stream
ou PipeReader

Novas sobrecargas de Results.Stream


Introduzimos novas Results.Stream sobrecargas para acomodar cenários que precisam
de acesso ao fluxo de resposta HTTP subjacente sem buffer. Essas sobrecargas também
melhoram os casos em que uma API transmite dados para o fluxo de resposta HTTP,
como de Armazenamento de Blobs do Azure. O exemplo a seguir usa ImageSharp
para retornar um tamanho reduzido da imagem especificada:

C#
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http,


CancellationToken token) =>
{
http.Response.Headers.CacheControl = $"public,max-age=
{TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(stream => ResizeImageAsync(strImage, stream,
token), "image/jpeg");
});

async Task ResizeImageAsync(string strImage, Stream stream,


CancellationToken token)
{
var strPath = $"wwwroot/img/{strImage}";
using var image = await Image.LoadAsync(strPath, token);
int width = image.Width / 2;
int height = image.Height / 2;
image.Mutate(x =>x.Resize(width, height));
await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken:
token);
}

Para obter mais informações, consulte Exemplos de fluxo

Resultados tipado para APIs mínimas


No .NET 6, a IResult interface foi introduzida para representar valores retornados de
APIs mínimas que não utilizam o suporte implícito para JSON serializando o objeto
retornado para a resposta HTTP. A classe Resultados estáticos é usada para criar objetos
variados IResult que representam diferentes tipos de respostas. Por exemplo, definir o
código de status de resposta ou redirecionar para outra URL. No IResult entanto, os
tipos de estrutura de implementação retornados desses métodos eram internos,
dificultando a verificação do tipo específico IResult que está sendo retornado de
métodos em um teste de unidade.

No .NET 7, os tipos que implementam IResult são públicos, permitindo declarações de


tipo ao testar. Por exemplo:

C#
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}

Melhor capacidade de teste de unidade para


manipuladores de rota mínimos
IResult Os tipos de implementação agora estão disponíveis publicamente no
Microsoft.AspNetCore.Http.HttpResults namespace . Os IResult tipos de
implementação podem ser usados para testar a unidade de manipuladores de rotas
mínimas ao usar métodos nomeados em vez de lambdas.

O código a seguir usa a Ok<TValue> classe :

C#

[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
// Arrange
await using var context = new MockDb().CreateDbContext();

context.Todos.Add(new Todo
{
Id = 1,
Title = "Test title",
Description = "Test description",
IsDone = false
});

await context.SaveChangesAsync();

// Act
var okResult = (Ok<Todo>)await TodoEndpointsV1.GetTodo(1, context);

//Assert
Assert.Equal(200, okResult.StatusCode);
var foundTodo = Assert.IsAssignableFrom<Todo>(okResult.Value);
Assert.Equal(1, foundTodo.Id);
}
Para obter mais informações, consulte IResult tipos de implementação.

Novas interfaces HttpResult


As interfaces a seguir no Microsoft.AspNetCore.Http namespace fornecem uma maneira
de detectar o IResult tipo em runtime, que é um padrão comum em implementações
de filtro:

IContentTypeHttpResult
IFileHttpResult
INestedHttpResult
IStatusCodeHttpResult
IValueHttpResult
IValueHttpResult<TValue>

Para obter mais informações, consulte Interfaces IHttpResult.

Melhorias de OpenAPI para APIs mínimas

Pacote NuGet Microsoft.AspNetCore.OpenApi

O Microsoft.AspNetCore.OpenApi pacote permite interações com especificações de


OpenAPI para pontos de extremidade. O pacote atua como um link entre os modelos
OpenAPI definidos no Microsoft.AspNetCore.OpenApi pacote e os pontos de
extremidade definidos em APIs mínimas. O pacote fornece uma API que examina
parâmetros, respostas e metadados de um ponto de extremidade para construir um tipo
de anotação OpenAPI usado para descrever um ponto de extremidade.

C#

app.MapPost("/todoitems/{id}", async (int id, Todo todo, TodoDb db) =>


{
todo.Id = id;
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


})
.WithOpenApi();

Chamar WithOpenApi com parâmetros


O WithOpenApi método aceita uma função que pode ser usada para modificar a
anotação OpenAPI. Por exemplo, no código a seguir, uma descrição é adicionada ao
primeiro parâmetro do ponto de extremidade:

C#

app.MapPost("/todo2/{id}", async (int id, Todo todo, TodoDb db) =>


{
todo.Id = id;
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


})
.WithOpenApi(generatedOperation =>
{
var parameter = generatedOperation.Parameters[0];
parameter.Description = "The ID associated with the created Todo";
return generatedOperation;
});

Fornecer resumos e descrições de ponto de extremidade


ApIs mínimas agora dão suporte à anotação de operações com descrições e resumos
para a geração de especificações do OpenAPI. Você pode chamar métodos
WithDescription de extensão e WithSummary ou usar atributos [EndpointDescription] e
[EndpointSummary]).

Para obter mais informações, consulte OpenAPI em aplicativos de API mínimos

Uploads de arquivo usando IFormFile e


IFormFileCollection
ApIs mínimas agora dão suporte ao upload de arquivo com IFormFile e
IFormFileCollection . O código a seguir usa IFormFile e IFormFileCollection para

carregar o arquivo:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>


{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>


{
foreach (var file in myFiles)
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
}
});

app.Run();

Há suporte para solicitações de upload de arquivo autenticadas usando um cabeçalho


de autorização , um certificado de cliente ou um cookie cabeçalho.

Não há suporte interno para antiforgeria. No entanto, ele pode ser implementado
usando o IAntiforgery serviço .

[AsParameters] o atributo habilita a associação de


parâmetros para listas de argumentos
O [AsParameters] atributo habilita a associação de parâmetros para listas de
argumentos. Para obter mais informações, consulte Associação de parâmetros para
listas de argumentos com [AsParameters].

APIs mínimas e controladores de API

Novo serviço de detalhes do problema


O serviço de detalhes do problema implementa a IProblemDetailsService interface , que
dá suporte à criação de Detalhes do Problema para APIs HTTP .

Para obter mais informações, consulte Serviço de detalhes do problema.

Grupos de rotas
O MapGroup método de extensão ajuda a organizar grupos de pontos de extremidade
com um prefixo comum. Ele reduz o código repetitivo e permite personalizar grupos
inteiros de pontos de extremidade com uma única chamada a métodos como
RequireAuthorization e WithMetadata que adicionam metadados de ponto de
extremidade.

Por exemplo, o código a seguir cria dois grupos semelhantes de pontos de extremidade:

C#

app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");

app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();

EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext
factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;

foreach (var argument in factoryContext.MethodInfo.GetParameters())


{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}

// Skip filter if the method doesn't have a TodoDb parameter.


if (dbContextIndex < 0)
{
return next;
}

return async invocationContext =>


{
var dbContext = invocationContext.GetArgument<TodoDb>
(dbContextIndex);
dbContext.IsPrivate = true;

try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise
reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}

C#

public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)


{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);

return group;
}

Nesse cenário, você pode usar um endereço relativo para o Location cabeçalho no 201
Created resultado:

C#

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb


database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();

return TypedResults.Created($"{todo.Id}", todo);


}

O primeiro grupo de pontos de extremidade corresponderá apenas às solicitações


prefixadas com /public/todos e estarão acessíveis sem nenhuma autenticação. O
segundo grupo de pontos de extremidade corresponderá apenas às solicitações
prefixadas com /private/todos e exigirá autenticação.

A QueryPrivateTodos fábrica de filtros de ponto de extremidade é uma função local que


modifica os parâmetros do manipulador de TodoDb rotas para permitir o acesso e o
armazenamento de dados todo privados.

Os grupos de rotas também dão suporte a grupos aninhados e padrões de prefixo


complexos com parâmetros de rota e restrições. No exemplo a seguir, o manipulador de
rotas mapeado para o user grupo pode capturar os {org} parâmetros de rota e
{group} definidos nos prefixos do grupo externo.

O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou
filtros de ponto de extremidade a um grupo de pontos de extremidade sem alterar o
padrão de rota.

C#

var all = app.MapGroup("").WithOpenApi();


var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

Adicionar filtros ou metadados a um grupo se comporta da mesma maneira que


adicioná-los individualmente a cada ponto de extremidade antes de adicionar filtros ou
metadados extras que possam ter sido adicionados a um grupo interno ou ponto de
extremidade específico.

C#

var outer = app.MapGroup("/outer");


var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/inner group filter");
return next(context);
});

outer.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/outer group filter");
return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("MapGet filter");
return next(context);
});

No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro


interno, mesmo que tenha sido adicionado em segundo lugar. Como os filtros foram
aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos
outros não importa. Os filtros de pedido são adicionados importam se aplicados ao
mesmo grupo ou ponto de extremidade específico.
Uma solicitação para /outer/inner/ registrará o seguinte:

CLI do .NET

/outer group filter


/inner group filter
MapGet filter

gRPC

JSTranscodificação ON
A transcodificação gRPC JSON é uma extensão para ASP.NET Core que cria RESTAPIs on
ful JSpara serviços gRPC. A transcodificação gRPC JSON permite:

Aplicativos para chamar serviços gRPC com conceitos HTTP conhecidos.


ASP.NET Core aplicativos gRPC para dar suporte a APIs gRPC e RESTFUL JSON sem
replicar a funcionalidade.
Suporte experimental para gerar OpenAPI de APIs ful transcodificadas
RESTintegrando-se ao Swashbuckle.

Para obter mais informações, consulte transcodificação gRPC JSON em aplicativos gRPC
ASP.NET Core e Usar OpenAPI com transcodificação gRPC JSON ASP.NET Core
aplicativos.

Verificações de integridade do gRPC no ASP.NET Core


O protocolo de verificação de integridade gRPC é um padrão para relatar a
integridade de aplicativos de servidor gRPC. Um aplicativo expõe verificações de
integridade como um serviço gRPC. Normalmente, eles são usados com um serviço de
monitoramento externo para verificar o status de um aplicativo.

O gRPC ASP.NET Core adicionou suporte interno para verificações de integridade do


gRPC com o Grpc.AspNetCore.HealthChecks pacote. Os resultados das verificações de
integridade do .NET são relatados aos chamadores.

Para obter mais informações, consulte verificações de integridade do gRPC no ASP.NET


Core.

Suporte aprimorado a credenciais de chamada


As credenciais de chamada são a maneira recomendada de configurar um cliente gRPC
para enviar um token de autenticação para o servidor. Os clientes gRPC dão suporte a
dois novos recursos para facilitar o uso de credenciais de chamada:

Suporte para credenciais de chamada com conexões de texto sem formatação.


Anteriormente, uma chamada gRPC só enviava credenciais de chamada se a
conexão fosse protegida com TLS. Uma nova configuração em
GrpcChannelOptions , chamada UnsafeUseInsecureChannelCallCredentials , permite
que esse comportamento seja personalizado. Há implicações de segurança para
não proteger uma conexão com o TLS.
Um novo método chamado AddCallCredentials está disponível com a fábrica de
clientes gRPC. AddCallCredentials é uma maneira rápida de configurar credenciais
de chamada para um cliente gRPC e integra-se bem à DI (injeção de dependência).

O código a seguir configura a fábrica de clientes gRPC para enviar Authorization


metadados:

C#

builder.Services
.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
})
.AddCallCredentials((context, metadata) =>
{
if (!string.IsNullOrEmpty(_token))
{
metadata.Add("Authorization", $"Bearer {_token}");
}
return Task.CompletedTask;
});

Para obter mais informações, consulte Configurar um token de portador com a fábrica
de clientes gRPC.

SignalR

Resultados do cliente
O servidor agora dá suporte à solicitação de um resultado de um cliente. Isso exige que
o servidor use ISingleClientProxy.InvokeAsync e que o cliente retorne um resultado de
seu .On manipulador. Hubs fortemente tipados também podem retornar valores de
métodos de interface.
Para obter mais informações, consulte Resultados do cliente

Injeção de dependência para SignalR métodos de hub


SignalR Os métodos hub agora dão suporte à injeção de serviços por meio de DI
(injeção de dependência).

Os construtores de hub podem aceitar serviços de DI como parâmetros, que podem ser
armazenados em propriedades na classe para uso em um método de hub. Para obter
mais informações, consulte Injetar serviços em um hub

Blazor

Manipular eventos de alteração de local e estado de


navegação
No .NET 7, dá Blazor suporte à alteração de local de eventos e à manutenção do estado
de navegação. Isso permite que você avise os usuários sobre o trabalho não salvo ou
execute ações relacionadas quando o usuário executa uma navegação de página.

Para obter mais informações, consulte as seções a seguir do artigo Roteamento e


navegação :

Opções de navegação
Manipular/impedir alterações de local

Modelos de projeto vazios Blazor


Blazor tem dois novos modelos de projeto para começar a partir de uma lousa em
branco. Os novos Blazor Server modelos de projeto App Empty e Blazor WebAssembly
App Empty são exatamente como seus equivalentes não vazios, mas sem código de
exemplo. Esses modelos vazios incluem apenas uma home page básica e removemos o
Bootstrap para que você possa começar com uma estrutura CSS diferente.

Para obter mais informações, consulte os seguintes artigos:

Ferramentas para ASP.NET Core Blazor


Blazor estrutura do projeto ASP.NET Core

Elementos personalizados de Blazor


O Microsoft.AspNetCore.Components.CustomElements pacote permite criar
elementos DOM personalizados baseados em padrões usando Blazor.

Para obter mais informações, consulte ASP.NET Core Razor componentes.

Associar modificadores ( @bind:after , @bind:get ,


@bind:set )

) Importante

Os @bind:after // @bind:get @bind:set recursos estão recebendo mais atualizações


no momento. Para aproveitar as atualizações mais recentes, confirme se você
instalou o SDK mais recente.

Não há suporte para o uso de um parâmetro de retorno de chamada de evento


( [Parameter] public EventCallback<string> ValueChanged { get; set; } ). Em vez
disso, passe um Actionmétodo -returning ou Task-returning
para/ @bind:set @bind:after .

Para saber mais, consulte os recursos a seguir:

Blazor@bind:after não funcionando na versão rtm do .NET 7


(dotnet/aspnetcore #44957)
BindGetSetAfter701 aplicativo de exemplo ( javiercn/BindGetSetAfter701
repositório GitHub)

No .NET 7, você pode executar a lógica assíncrona após a conclusão de um evento de


associação usando o novo @bind:after modificador. No exemplo a seguir, o método
assíncrono PerformSearch é executado automaticamente após qualquer alteração no
texto de pesquisa ser detectada:

razor

<input @bind="searchText" @bind:after="PerformSearch" />

@code {
private string searchText;

private async Task PerformSearch()


{
...
}
}
No .NET 7, também é mais fácil configurar a associação para parâmetros de
componente. Os componentes podem dar suporte à associação de dados bidirecional
definindo um par de parâmetros:

@bind:get : especifica o valor a ser associado.


@bind:set : especifica um retorno de chamada para quando o valor é alterado.

Os @bind:get modificadores e @bind:set são sempre usados juntos.

Exemplos:

razor

@* Elements *@

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind:get="text" @bind:set="(value) => { }" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<input type="text" @bind:get="text" @bind:set="SetAsync" />

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind:get="text" @bind:set="(value) => { }" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<input type="text" @bind:get="text" @bind:set="SetAsync" />

@* Components *@

<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value:get="text" @bind-Value:set="(value) => { }" />

<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />

<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value:get="text" @bind-Value:set="(value) => { }" />

<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />

<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

@code {
private string text = "";
private void After(){}
private void Set() {}
private Task AfterAsync() { return Task.CompletedTask; }
private Task SetAsync(string value) { return Task.CompletedTask; }
}

Para obter mais informações sobre o InputText componente, consulte ASP.NET Core
Blazor formulários e componentes de entrada.

melhorias Recarga Dinâmica


No .NET 7, Recarga Dinâmica suporte inclui o seguinte:

Os componentes redefinem seus parâmetros para seus valores padrão quando um


valor é removido.
Blazor WebAssembly:
Adicione novos tipos.
Adicionar classes aninhadas.
Adicione métodos estáticos e de instância a tipos existentes.
Adicione campos e métodos estáticos a tipos existentes.
Adicione lambdas estáticos aos métodos existentes.
Adicione lambdas que capturam this métodos existentes que já foram
capturados this anteriormente.

Solicitações de autenticação dinâmica com MSAL no


Blazor WebAssembly
Novo no .NET 7, Blazor WebAssembly dá suporte à criação de solicitações de
autenticação dinâmica em runtime com parâmetros personalizados para lidar com
cenários de autenticação avançada.

Para obter mais informações, consulte os seguintes artigos:

Proteger ASP.NET Core Blazor WebAssembly


Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly

Blazor WebAssembly aprimoramentos de depuração


Blazor WebAssembly A depuração tem as seguintes melhorias:

Suporte para a configuração Apenas Meu Código para mostrar ou ocultar


membros de tipo que não são do código do usuário.
Suporte para inspecionar matrizes multidimensionais.
Pilha de Chamadas agora mostra o nome correto para métodos assíncronos.
Avaliação de expressão aprimorada.
Tratamento correto da new palavra-chave em membros derivados.
Suporte para atributos relacionados ao depurador no System.Diagnostics .

System.Security.Cryptography suporte no WebAssembly

O .NET 6 deu suporte à família SHA de algoritmos de hash durante a execução no


WebAssembly. O .NET 7 permite mais algoritmos criptográficos aproveitando
SubtleCrypto , quando possível, e voltando para uma implementação do .NET quando
SubtleCrypto não pode ser usado. Os seguintes algoritmos têm suporte no
WebAssembly no .NET 7:

SHA1
SHA256
SHA384
SHA512
HMACSHA1
HMACSHA256
HMACSHA384
HMACSHA512
AES-CBC
PBKDF2
HKDF

Para obter mais informações, consulte Os desenvolvedores direcionados ao browser-


wasm podem usar APIs de criptografia da Web (dotnet/runtime #40074) .

Injetar serviços em atributos de validação personalizados


Agora você pode injetar serviços em atributos de validação personalizados. Blazor
configura o ValidationContext para que ele possa ser usado como um provedor de
serviços.

Para obter mais informações, consulte ASP.NET Core Blazor formulários e componentes
de entrada.

Input* componentes fora de um EditContext / EditForm


Os componentes de entrada internos agora têm suporte fora de um formulário na Razor
marcação de componente.

Para obter mais informações, consulte ASP.NET Core Blazor formulários e componentes
de entrada.

Alterações no modelo de projeto


Quando o .NET 6 foi lançado no ano passado, a marcação HTML da _Host página
( Pages/_Host.chstml ) foi dividida entre a _Host página e uma nova _Layout página
( Pages/_Layout.chstml ) no modelo de projeto do .NET 6 Blazor Server .

No .NET 7, a marcação HTML foi recombinada com a _Host página em modelos de


projeto.

Várias alterações adicionais foram feitas nos modelos de Blazor projeto. Não é viável
listar todas as alterações nos modelos na documentação. Para migrar um aplicativo para
o .NET 7 para adotar todas as alterações, consulte Migrar de ASP.NET Core 6.0 para 7.0.

Componente experimental QuickGrid


O novo QuickGrid componente fornece um componente de grade de dados
conveniente para os requisitos mais comuns e como uma linha de base de desempenho
e arquitetura de referência para qualquer pessoa que crie Blazor componentes de grade
de dados.

Para obter mais informações, consulte componentes ASP.NET CoreRazor.

Demonstração ao vivo: QuickGrid para Blazor aplicativo de exemplo

Aprimoramentos de virtualização
Aprimoramentos de virtualização no .NET 7:

O Virtualize componente dá suporte ao uso do documento em si como a raiz de


rolagem, como uma alternativa para ter algum outro elemento com overflow-y:
scroll aplicado.

Se o Virtualize componente for colocado dentro de um elemento que requer um


nome de marca filho específico, SpacerElement permitirá que você obtenha ou
defina o nome da marca do espaçador de virtualização.

Para obter mais informações, consulte as seguintes seções do artigo Virtualização :


Virtualização de nível raiz
Controlar o nome da marca de elemento do espaçador

MouseEventArgs Atualizações

MovementX e MovementY foram adicionados a MouseEventArgs .

Para obter mais informações, consulte ASP.NET Core Blazor manipulação de eventos.

Nova Blazor página de carregamento


O Blazor WebAssembly modelo de projeto tem uma nova interface do usuário de
carregamento que mostra o progresso do carregamento do aplicativo.

Para obter mais informações, consulte ASP.NET Core Blazor inicialização.

Diagnóstico aprimorado para autenticação no Blazor


WebAssembly
Para ajudar a diagnosticar problemas de autenticação em Blazor WebAssembly
aplicativos, o log detalhado está disponível.

Para obter mais informações, consulte ASP.NET Core Blazor registro em log.

Interoperabilidade do JavaScript no WebAssembly


A API de interoperabilidade do JavaScript [JSImport] / [JSExport] é um novo
mecanismo de baixo nível para usar o .NET em aplicativos baseados em Blazor
WebAssembly JavaScript e . Com essa nova funcionalidade de interoperabilidade do
JavaScript, você pode invocar o código .NET do JavaScript usando o runtime do
WebAssembly do .NET e chamar a funcionalidade JavaScript do .NET sem nenhuma
dependência no modelo de componente da interface do Blazor usuário.

Para mais informações:

Javascript JS Interoperabilidade de importação/JSexportação com ASP.NET Core


Blazor WebAssembly: pertence somente a Blazor WebAssembly aplicativos.
Executar o .NET do JavaScript: pertence somente a aplicativos JavaScript que não
dependem do modelo de componente da interface do Blazor usuário.
Registro condicional do provedor de estado de
autenticação
Antes do lançamento do .NET 7, AuthenticationStateProvider era registrado no
contêiner de serviço com AddScoped . Isso dificulta a depuração de aplicativos, pois
forçou uma ordem específica de registros de serviço ao fornecer uma implementação
personalizada. Devido a alterações na estrutura interna ao longo do tempo, não é mais
necessário se registrar AuthenticationStateProvider no AddScoped .

No código do desenvolvedor, faça a seguinte alteração no registro de serviço do


provedor de estado de autenticação:

diff

- builder.Services.AddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
+ builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();

No exemplo anterior, ExternalAuthStateProvider é a implementação do serviço do


desenvolvedor.

Melhorias nas ferramentas de build do WebAssembly do


.NET
Novos recursos na carga de trabalho do wasm-tools .NET 7 que ajudam a melhorar o
desempenho e lidar com exceções:

WebAssembly Single Instruction, Suporte a VÁRIOS Dados (SIMD) (somente com


AOT, sem suporte do Apple Safari)
Suporte ao tratamento de exceções do WebAssembly

Para obter mais informações, consulte Ferramentas para ASP.NET Core Blazor.

Blazor Hybrid

URLs externas
Foi adicionada uma opção que permite abrir páginas da Web externas no navegador.

Para obter mais informações, consulte roteamento e navegação ASP.NET CoreBlazor


Hybrid.
Segurança
Novas diretrizes estão disponíveis para Blazor Hybrid cenários de segurança. Para obter
mais informações, consulte os seguintes artigos:

Autenticação e autorização de Blazor Hybrid no ASP.NET Core


Considerações de segurança do ASP.NET Core Blazor Hybrid

Desempenho

Middleware de cache de saída


O cache de saída é um novo middleware que armazena respostas de um aplicativo Web
e as serve de um cache em vez de computá-las todas as vezes. O cache de saída difere
do cache de resposta das seguintes maneiras:

O comportamento de cache é configurável no servidor.


As entradas de cache podem ser invalidadas programaticamente.
O bloqueio de recursos reduz o risco de debandada de cache e rebanho
estrondoso .
A revalidação de cache significa que o servidor pode retornar um 304 Not
Modified código de status HTTP em vez de um corpo de resposta armazenado em

cache.
A mídia de armazenamento em cache é extensível.

Para obter mais informações, consulte Visão geral do middleware de cache e cache de
saída.

Melhorias de HTTP/3
Esta versão:

Torna o HTTP/3 totalmente compatível com ASP.NET Core, ele não é mais
experimental.
Melhora o Kestrelsuporte do para HTTP/3. As duas principais áreas de melhoria
são a paridade de recursos com HTTP/1.1 e HTTP/2 e desempenho.
Fornece suporte completo para UseHttps(ListenOptions, X509Certificate2) com
HTTP/3. Kestreloferece opções avançadas para configurar certificados de conexão,
como conectar-se à SNI (Indicação de Nome do Servidor).
Adiciona suporte para HTTP/3 em HTTP.sys e IIS.
O exemplo a seguir mostra como usar um retorno de chamada SNI para resolver opções
de TLS:

C#

using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(8080, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
listenOptions.UseHttps(new TlsHandshakeCallbackOptions
{
OnConnection = context =>
{
var options = new SslServerAuthenticationOptions
{
ServerCertificate =

MyResolveCertForHost(context.ClientHelloInfo.ServerName)
};
return new ValueTask<SslServerAuthenticationOptions>
(options);
},
});
});
});

Foi feito um trabalho significativo no .NET 7 para reduzir as alocações http/3. Você pode
ver algumas dessas melhorias nas seguintes PR do GitHub:

HTTP/3: evitar alocações de token de cancelamento por solicitação


HTTP/3: evitar alocações connectionAbortedException
HTTP/3: pooling valueTask

Melhorias de desempenho http/2


O .NET 7 apresenta uma arquitetura significativa de como Kestrel processa solicitações
HTTP/2. ASP.NET Core aplicativos com conexões HTTP/2 ocupadas terão uso reduzido
da CPU e maior taxa de transferência.

Anteriormente, a implementação de multiplexação HTTP/2 dependia de um bloqueio


que controlava qual solicitação pode gravar na conexão TCP subjacente. Uma fila
thread-safe substitui o bloqueio de gravação. Agora, em vez de lutar sobre qual
thread pode usar o bloqueio de gravação, as solicitações agora são enfileiradas e um
consumidor dedicado as processa. Os recursos de CPU desperdiçados anteriormente
estão disponíveis para o restante do aplicativo.

Um local em que essas melhorias podem ser observadas é no gRPC, uma estrutura RPC
popular que usa HTTP/2. Kestrel + Os parâmetros de comparação de gRPC mostram
uma melhoria dramática:

Foram feitas alterações no código de gravação de quadro HTTP/2 que melhora o


desempenho quando há vários fluxos tentando gravar dados em uma única conexão
HTTP/2. Agora, enviamos o trabalho do TLS para o pool de threads e liberamos mais
rapidamente um bloqueio de gravação que outros fluxos podem adquirir para gravar
seus dados. A redução dos tempos de espera pode gerar melhorias significativas de
desempenho nos casos em que há contenção para esse bloqueio de gravação. Um
parâmetro de comparação gRPC com 70 fluxos em uma única conexão (com TLS)
mostrou uma melhoria de aproximadamente 15% nas solicitações por segundo (RPS)
com essa alteração.

Suporte a WebSockets http/2


O .NET 7 apresenta o suporte a Websockets por HTTP/2 para Kestrel, o SignalR cliente
JavaScript e SignalR com Blazor WebAssembly.
O uso de WebSockets por HTTP/2 aproveita os novos recursos, como:

Compactação de cabeçalho.
Multiplexação, que reduz o tempo e os recursos necessários ao fazer várias
solicitações ao servidor.

Esses recursos com suporte estão disponíveis em Kestrel todas as plataformas


habilitadas para HTTP/2. A negociação de versão é automática em navegadores e
Kestrel, portanto, nenhuma nova APIs é necessária.

Para obter mais informações, consulte Suporte a WebSockets Http/2.

Kestrel melhorias de desempenho em computadores de


alto núcleo
Kestrel usa ConcurrentQueue<T> para muitas finalidades. Uma das finalidades é
agendar operações de E/S no Kestreltransporte de soquete padrão. Particionar o
ConcurrentQueue com base no soquete associado reduz a contenção e aumenta a taxa
de transferência em computadores com muitos núcleos de CPU.

A criação de perfil em computadores de núcleo alto no .NET 6 mostrou contenção


significativa em uma das Kestreloutras ConcurrentQueue instâncias do , a
PinnedMemoryPool que Kestrel usa para armazenar buffers de bytes em cache.

No .NET 7, Kestrelo pool de memória é particionado da mesma maneira que sua fila de
E/S, o que leva a uma contenção muito menor e maior taxa de transferência em
computadores de núcleo alto. Nas VMs ARM64 de 80 núcleos, estamos vendo uma
melhoria de mais de 500% nas RPS (respostas por segundo) no parâmetro de
comparação de texto sem formatação TechEmpower. Em VMs AMD de 48 Núcleos, o
aprimoramento é de quase 100% em nosso parâmetro de comparação HTTPS JSON.

ServerReady evento para medir o tempo de inicialização

Os aplicativos que usam o EventSource podem medir o tempo de inicialização para


entender e otimizar o desempenho da inicialização. O novo ServerReady evento em
representa o ponto em Microsoft.AspNetCore.Hosting que o servidor está pronto para
responder às solicitações.

Servidor
Novo evento ServerReady para medir o tempo de
inicialização
O ServerReady evento foi adicionado para medir o tempo de inicialização de
aplicativos ASP.NET Core.

IIS

Cópia de sombra no IIS


A cópia de assemblies de aplicativos de sombra para o ANCM (Módulo ASP.NET Core)
para IIS pode fornecer uma experiência melhor do usuário final do que interromper o
aplicativo implantando um arquivo offline do aplicativo.

Para obter mais informações, consulte Cópia de sombra no IIS.

Diversos

Kestrel aprimoramentos completos da cadeia de


certificados
HttpsConnectionAdapterOptions tem uma nova propriedade ServerCertificateChain do
tipo X509Certificate2Collection, o que facilita a validação de cadeias de certificados,
permitindo que uma cadeia completa, incluindo certificados intermediários, seja
especificada. Consulte dotnet/aspnetcore#21513 para obter mais detalhes.

dotnet watch

Saída aprimorada do console para dotnet watch

A saída do console do relógio dotnet foi aprimorada para se alinhar melhor ao registro
em log de ASP.NET Core e se destacar com 😮emojis😍.

Aqui está um exemplo de como a nova saída se parece:


Para obter mais informações, consulte esta solicitação de pull do GitHub .

Configurar o dotnet watch para sempre reiniciar para


edições rudes
Edições rudes são edições que não podem ser recarregadas quentes. Para configurar o
dotnet watch para sempre reiniciar sem um prompt para edições rudes, defina a variável
de DOTNET_WATCH_RESTART_ON_RUDE_EDIT ambiente como true .

Modo escuro da página de exceção do desenvolvedor


O suporte ao modo escuro foi adicionado à página de exceção do desenvolvedor,
graças a uma contribuição de Patrick Westerhoff . Para testar o modo escuro em um
navegador, na página ferramentas de desenvolvedor, defina o modo como escuro. Por
exemplo, no Firefox:
No Chrome:

Opção de modelo de projeto para usar o método


Program.Main em vez de instruções de nível superior
Os modelos do .NET 7 incluem uma opção para não usar instruções de nível superior e
gerar um namespace método e Main declarado em uma Program classe.

Usando a CLI do .NET, use a opção --use-program-main :

CLI do .NET

dotnet new web --use-program-main

Com o Visual Studio, marque a nova caixa de seleção Não usar instruções de nível
superior durante a criação do projeto:
Modelos de Angular e React atualizados
O modelo de projeto Angular foi atualizado para Angular 14. O modelo de projeto
React foi atualizado para React 18.2.

Gerenciar JSTokens Web ON em desenvolvimento com


dotnet user-jwts
A nova dotnet user-jwts ferramenta de linha de comando pode criar e gerenciar JWTs
(Tokens Web ON) locaisJS específicos do aplicativo. Para obter mais informações,
consulte Gerenciar JStokens Web ON em desenvolvimento com dotnet user-jwts.

Suporte para cabeçalhos de solicitação adicionais no


W3CLogger
Agora você pode especificar cabeçalhos de solicitação adicionais para registrar em log
ao usar o agente W3C chamando AdditionalRequestHeaders() em W3CLoggerOptions:

C#

services.AddW3CLogging(logging =>
{
logging.AdditionalRequestHeaders.Add("x-forwarded-for");
logging.AdditionalRequestHeaders.Add("x-client-ssl-protocol");
});

Para obter mais informações, consulte Opções do W3CLogger.

Solicitação de descompactação
O novo middleware de descompactação de solicitação:

Permite que os pontos de extremidade de API aceitem solicitações com conteúdo


compactado.
Usa o Content-Encoding cabeçalho HTTP para identificar e descompactar
automaticamente solicitações que contêm conteúdo compactado.
Elimina a necessidade de escrever código para lidar com solicitações compactadas.

Para obter mais informações, consulte Solicitar middleware de descompactação.


Novidades no ASP.NET Core 6.0
Artigo • 28/11/2022 • 31 minutos para o fim da leitura

Este artigo destaca as alterações mais significativas no ASP.NET Core 6.0 com links para
a documentação relevante.

ASP.NET Core MVC e Razor melhorias

APIs mínimas
APIs mínimas são arquitetas para criar APIs HTTP com dependências mínimas. Eles são
ideais para microsserviços e aplicativos que desejam incluir apenas os arquivos mínimos,
recursos e dependências em ASP.NET Core. Para obter mais informações, consulte:

Tutorial: Criar uma API Web mínima com ASP.NET Core


Diferenças entre APIs mínimas e APIs com controladores
Referência rápida de APIs mínimas
Exemplos de código migrados para o novo modelo de hospedagem mínima na
versão 6.0

SignalR

Marca de atividade de execução prolongada para SignalR


conexões
SignalR usa o novo Microsoft.AspNetCore.Http.Features.IHttpActivityFeature.Activity
para adicionar uma http.long_running marca à atividade de solicitação.
IHttpActivityFeature.Activity é usado por serviços do APM como o Application
Insights do Azure Monitor para filtrar SignalR solicitações da criação de alertas de
solicitação de execução prolongada.

Aprimoramentos no desempenho de SignalR


Aloque HubCallerClients uma vez por conexão em vez de cada chamada de
método de hub.
Evite a alocação de fechamento em SignalR DefaultHubDispatcher.Invoke . O estado
é passado para uma função estática local por meio de parâmetros para evitar uma
alocação de fechamento. Para obter mais informações, consulte esta solicitação de
pull do GitHub .
Aloque um único StreamItemMessage por fluxo em vez de por item de fluxo no
streaming de servidor para cliente. Para obter mais informações, consulte esta
solicitação de pull do GitHub .

Razor Compilador

Razor compilador atualizado para usar geradores de


origem
O Razor compilador agora é baseado em geradores de origem C#. Os geradores de
origem são executados durante a compilação e inspecionam o que está sendo
compilado para produzir arquivos adicionais compilados junto com o restante do
projeto. O uso de geradores de origem simplifica o Razor compilador e acelera
significativamente os tempos de compilação.

Razor O compilador não produz mais um assembly de


Exibições separado
O Razor compilador utilizou anteriormente um processo de compilação em duas etapas
que produziu um assembly views separado que continha as exibições e páginas geradas
( .cshtml arquivos) definidas no aplicativo. Os tipos gerados eram públicos e sob o
AspNetCore namespace .

O compilador atualizado Razor cria os tipos de exibições e páginas no assembly


principal do projeto. Esses tipos agora são gerados por padrão como lacrados internos
no AspNetCoreGeneratedDocument namespace . Essa alteração melhora o desempenho do
build, permite a implantação de arquivo único e permite que esses tipos participem de
Recarga Dinâmica.

Para obter mais informações sobre essa alteração, consulte o problema de comunicado
relacionado no GitHub.

melhorias de desempenho e API do ASP.NET


Core
Muitas alterações foram feitas para reduzir as alocações e melhorar o desempenho em
toda a pilha:
Aplicativo não alocador. Use o método de extensão. A nova sobrecarga de
app.Use requer passar o contexto para next o qual salva duas alocações internas
por solicitação necessárias ao usar a outra sobrecarga.
Redução de alocações de memória ao acessar HttpRequest.Cookies. Saiba mais
neste tópico do GitHub .
Use LoggerMessage.Define para as janelas somente HTTP.sys servidor Web. As
ILogger chamadas de métodos de extensão foram substituídas por chamadas para
LoggerMessage.Define .

Reduza a sobrecarga por conexão em SocketConnection em aproximadamente


30%. Para obter mais informações, consulte esta solicitação de pull do GitHub .
Reduza as alocações removendo delegados de log em tipos genéricos. Para obter
mais informações, consulte esta solicitação de pull do GitHub .
Acesso GET mais rápido (cerca de 50%) a recursos comumente usados, como
IHttpRequestFeature, IHttpResponseFeature, IHttpResponseBodyFeature,
IRouteValuesFeaturee IEndpointFeature. Para obter mais informações, consulte esta
solicitação de pull do GitHub .
Use cadeias de caracteres de instância única para nomes de cabeçalho conhecidos,
mesmo que não estejam no bloco de cabeçalho preservado. O uso de cadeia de
caracteres de instância única ajuda a evitar várias duplicatas da mesma cadeia de
caracteres em conexões de longa duração, por exemplo, em
Microsoft.AspNetCore.WebSockets. Saiba mais neste tópico do GitHub .
Reutilize HttpProtocol CancellationTokenSource em Kestrel. Use o novo método
CancellationTokenSource.TryReset em CancellationTokenSource para reutilizar
tokens se eles não tiverem sido cancelados. Para obter mais informações, consulte
este problema do GitHub e este vídeo .
Implemente e use um AdaptiveCapacityDictionary na
Microsoft.AspNetCore.HttpColeção de SolicitaçõesCookie para obter acesso
mais eficiente aos dicionários. Para obter mais informações, consulte esta
solicitação de pull do GitHub .

Volume de memória reduzido para conexões TLS ociosas


Para conexões TLS de longa execução em que os dados são enviados apenas
ocasionalmente para frente e para trás, reduzimos significativamente o volume de
memória de aplicativos ASP.NET Core no .NET 6. Isso deve ajudar a melhorar a
escalabilidade de cenários como servidores WebSocket. Isso foi possível devido a várias
melhorias em System.IO.Pipelines, SslStreame Kestrel. As seções a seguir detalham
algumas das melhorias que contribuíram para a redução do volume de memória:

Reduzir o tamanho de System.IO.Pipelines.Pipe


Para cada conexão estabelecida, dois pipes são alocados em Kestrel:

A camada de transporte para o aplicativo para a solicitação.


A camada de aplicativo para o transporte para a resposta.

Ao reduzir o tamanho de System.IO.Pipelines.Pipe 368 bytes para 264 bytes (cerca de


28,2% de redução), 208 bytes por conexão são salvos (104 bytes por Pipe).

Pool SocketSender
SocketSender os objetos (essa subclasse SocketAsyncEventArgs) têm cerca de 350 bytes

em runtime. Em vez de alocar um novo SocketSender objeto por conexão, eles podem
ser agrupados. SocketSender objetos podem ser agrupados porque os envios
geralmente são muito rápidos. O pooling reduz a sobrecarga por conexão. Em vez de
alocar 350 bytes por conexão, apenas pagar 350 bytes por IOQueue são alocados. A
alocação é feita por fila para evitar contenção. Nosso servidor WebSocket com 5.000
conexões ociosas passou da alocação de ~1,75 MB (350 bytes * 5000) para a alocação
de ~2,8 kb (350 bytes * 8) para SocketSender objetos.

Leituras de zero bytes com SslStream

As leituras sem buffer são uma técnica empregada em ASP.NET Core para evitar o
aluguel de memória do pool de memória se não houver dados disponíveis no soquete.
Antes dessa alteração, nosso servidor WebSocket com 5.000 conexões ociosas exigia
cerca de 200 MB sem TLS em comparação com cerca de 800 MB com TLS. Algumas
dessas alocações (4k por conexão) eram de Kestrel ter que manter um ArrayPool<T>
buffer enquanto aguardavam a conclusão das leituras SslStream . Considerando que
essas conexões estavam ociosas, nenhuma das leituras foi concluída e retornou seus
buffers para o ArrayPool , forçando o ArrayPool a alocar mais memória. As alocações
restantes estavam em SslStream si: buffer de 4k para handshakes TLS e buffer de 32k
para leituras normais. No .NET 6, quando o usuário executa uma leitura SslStream de
zero byte e não tem dados disponíveis, SslStream executa internamente uma leitura de
zero byte no fluxo encapsulado subjacente. Na melhor das hipóteses (conexão ociosa),
essas alterações resultam em uma economia de 40 Kb por conexão, ao mesmo tempo
em que permitem que o consumidor (Kestrel) seja notificado quando os dados
estiverem disponíveis sem manter os buffers não utilizados.

Leituras de bytes zero com PipeReader


Com leituras sem buffer com suporte em SslStream , uma opção foi adicionada para
executar leituras de bytes zero para StreamPipeReader , o tipo interno que adapta um
Stream em um PipeReader . No Kestrel, um StreamPipeReader é usado para adaptar o

subjacente SslStream a um PipeReader . Portanto, era necessário expor essas semânticas


de leitura de bytes zero no PipeReader .

Agora é possível criar um PipeReader que dá suporte a leituras de zero bytes em


qualquer subjacente Stream que dê suporte à semântica de leitura de bytes zero (por
exemplo, SslStream , NetworkStream, etc. usando a seguinte API:

CLI do .NET

var reader = PipeReader.Create(stream, new


StreamPipeReaderOptions(useZeroByteReads: true));

Remover lajes do SlabMemoryPool


Para reduzir a fragmentação do heap, Kestrel empregou uma técnica em que alocava
lajes de memória de 128 KB como parte de seu pool de memória. As lajes foram então
divididas ainda mais em blocos de 4 KB que foram usados internamente Kestrel . As
lajes tinham que ser maiores que 85 KB para forçar a alocação no heap de objetos
grandes para tentar impedir que o GC realocasse essa matriz. No entanto, com a
introdução da nova geração do GC, o POH ( Pinned Object Heap ), não faz mais
sentido alocar blocos na laje. Kestrel agora aloca diretamente blocos no POH, reduzindo
a complexidade envolvida no gerenciamento do pool de memória. Essa alteração deve
facilitar a execução de melhorias futuras, como facilitar a redução do pool de memória
usado pelo Kestrel.

IAsyncDisposable com suporte


IAsyncDisposable agora está disponível para controladores, Razor páginas e
componentes de exibição. Versões assíncronas foram adicionadas às interfaces
relevantes em fábricas e ativadores:

Os novos métodos oferecem uma implementação de interface padrão que delega


para a versão síncrona e chama Dispose.
As implementações substituem a implementação padrão e lidam com
implementações de IAsyncDisposable descarte.
As implementações favorecem IAsyncDisposable quando IDisposable ambas as
interfaces são implementadas.
Os extensores devem substituir os novos métodos incluídos para dar suporte
IAsyncDisposable a instâncias.

IAsyncDisposable é benéfico ao trabalhar com:

Enumeradores assíncronos, por exemplo, em fluxos assíncronos.


Recursos não gerenciados que têm operações de E/S com uso intensivo de
recursos a serem liberados.

Ao implementar essa interface, use o DisposeAsync método para liberar recursos.

Considere um controlador que cria e usa um Utf8JsonWriter. Utf8JsonWriter é um


IAsyncDisposable recurso:

C#

public class HomeController : Controller, IAsyncDisposable


{
private Utf8JsonWriter? _jsonWriter;
private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)


{
_logger = logger;
_jsonWriter = new Utf8JsonWriter(new MemoryStream());
}

IAsyncDisposable deve implementar DisposeAsync :

C#

public async ValueTask DisposeAsync()


{
if (_jsonWriter is not null)
{
await _jsonWriter.DisposeAsync();
}

_jsonWriter = null;
}

Porta vcpkg para SignalR o cliente C++


O Vcpkg é um gerenciador de pacotes de linha de comando multiplataforma para
bibliotecas C e C++. Recentemente, adicionamos uma porta para vcpkg adicionar CMake
suporte nativo ao SignalR cliente C++. vcpkg também funciona com o MSBuild.
O SignalR cliente pode ser adicionado a um projeto do CMake com o seguinte snippet
quando o vcpkg é incluído no arquivo de cadeia de ferramentas:

CLI do .NET

find_package(microsoft-signalr CONFIG REQUIRED)


link_libraries(microsoft-signalr::microsoft-signalr)

Com o snippet anterior, o SignalR cliente C++ está pronto para usar #include e usado
em um projeto sem nenhuma configuração adicional. Para obter um exemplo completo
de um aplicativo C++ que utiliza o SignalR cliente C++, consulte o repositório
halter73/SignalR-Client-Cpp-Sample .

Blazor

Alterações no modelo de projeto


Várias alterações de modelo de projeto foram feitas para Blazor aplicativos, incluindo o
uso do Pages/_Layout.cshtml arquivo para conteúdo de layout que apareceu no
_Host.cshtml arquivo para aplicativos anteriores Blazor Server . Estude as alterações

criando um aplicativo de um modelo de projeto 6.0 ou acessando o ASP.NET Core fonte


de referência para os modelos de projeto:

Blazor Server
Blazor WebAssembly

Blazor WebAssembly suporte a dependências nativas


Blazor WebAssembly os aplicativos podem usar dependências nativas criadas para
serem executadas no WebAssembly. Para obter mais informações, consulte ASP.NET
Core Blazor WebAssembly dependências nativas.

Compilação e revinculação de runtime do WebAssembly


Ahead-of-Time (AOT)
Blazor WebAssembly dá suporte à compilação AOT (antecipada), na qual você pode
compilar seu código .NET diretamente no WebAssembly. A compilação AOT resulta em
melhorias de desempenho de runtime em detrimento de um tamanho de aplicativo
maior. A revinculação do runtime webAssembly do .NET corta o código de runtime não
utilizado e, portanto, melhora a velocidade de download. Para obter mais informações,
consulte Compilação AOT (antecipada) e Revinculação de runtime.

Manter o estado pré-gerado


Blazor dá suporte ao estado persistente em uma página pré-gerada para que o estado
não precise ser recriado quando o aplicativo for totalmente carregado. Para obter mais
informações, consulte Pré-gerar e integrar componentes ASP.NET CoreRazor.

Limites de erro
Os limites de erro fornecem uma abordagem conveniente para lidar com exceções no
nível da interface do usuário. Para obter mais informações, consulte Tratar erros em
aplicativos ASP.NET CoreBlazor.

Suporte a SVG
O <foreignObject> elemento de elemento tem suporte para exibir HTML arbitrário
em um SVG. Para obter mais informações, consulte componentes ASP.NET CoreRazor.

Blazor Server suporte para transferência de matriz de


bytes na JS Interoperabilidade
Blazor dá suporte à interoperabilidade de matriz JS de bytes otimizada que evita a
codificação e a decodificação de matrizes de bytes em Base64. Para saber mais, consulte
os recursos a seguir:

Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor


Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor

Aprimoramentos de cadeia de caracteres de consulta


O suporte para trabalhar com cadeias de caracteres de consulta foi aprimorado. Para
obter mais informações, consulte roteamento e navegação ASP.NET CoreBlazor.

Associação para selecionar vários


A associação dá suporte à seleção de várias opções com <input> elementos. Para saber
mais, consulte os recursos a seguir:
Blazor ASP.NET Core associação de dados
Blazor ASP.NET Core formulários e componentes de entrada

Controle de conteúdo de cabeçalho ( <head> )


Razor os componentes podem modificar o conteúdo do elemento HTML <head> de
uma página, incluindo a definição do título da página ( <title> elemento) e a
modificação de metadados ( <meta> elementos). Para obter mais informações, consulte
Controlar <head> conteúdo em aplicativos ASP.NET CoreBlazor.

Gerar componentes Angular e React


Gere componentes JavaScript específicos da estrutura de Razor componentes para
estruturas da Web, como Angular ou React. Para obter mais informações, consulte
componentes ASP.NET CoreRazor.

Renderizar componentes do JavaScript


Renderize componentes dinamicamente Razor do JavaScript para aplicativos JavaScript
existentes. Para obter mais informações, consulte componentes ASP.NET CoreRazor.

Elementos personalizados
O suporte experimental está disponível para a criação de elementos personalizados, que
usam interfaces HTML padrão. Para obter mais informações, consulte componentes
ASP.NET CoreRazor.

Inferir tipos genéricos de componentes ancestrais


Um componente ancestral pode colocar em cascata um parâmetro de tipo por nome
para descendentes usando o novo [CascadingTypeParameter] atributo . Para obter mais
informações, consulte componentes ASP.NET CoreRazor.

Componentes renderizados dinamicamente


Use o novo componente interno DynamicComponent para renderizar componentes por
tipo. Para obter mais informações, consulte Componentes de ASP.NET Core Razor
renderizados dinamicamente.
Acessibilidade aprimorada Blazor
Use o novo FocusOnNavigate componente para definir o foco da interface do usuário
como um elemento com base em um seletor CSS depois de navegar de uma página
para outra. Para obter mais informações, consulte roteamento e navegação ASP.NET
CoreBlazor.

Suporte a argumento de evento personalizado


Blazor dá suporte a argumentos de evento personalizados, que permitem passar dados
arbitrários para manipuladores de eventos .NET com eventos personalizados. Para obter
mais informações, consulte ASP.NET Core Blazor manipulação de eventos.

Parâmetros obrigatórios
Aplique o novo [EditorRequired] atributo para especificar um parâmetro de
componente necessário. Para obter mais informações, consulte componentes ASP.NET
CoreRazor.

Ordenação de arquivos JavaScript com páginas, exibições


e componentes
Coloque arquivos JavaScript para páginas, exibições e Razor componentes como uma
maneira conveniente de organizar scripts em um aplicativo. Para obter mais
informações, consulte ASP.NET Core Blazor interoperabilidade do JavaScript
(JSinteroperabilidade).

Inicializadores de JavaScript
Os inicializadores do JavaScript executam a lógica antes e depois do carregamento de
um Blazor aplicativo. Para obter mais informações, consulte ASP.NET Core Blazor
interoperabilidade do JavaScript (JSinteroperabilidade).

Interoperabilidade do JavaScript de streaming


Blazor agora dá suporte a dados de streaming diretamente entre o .NET e o JavaScript.
Para saber mais, consulte os recursos a seguir:

Transmitir do .NET para o JavaScript


Transmitir do JavaScript para o .NET
Restrições de tipo genérico
Agora há suporte para parâmetros de tipo genérico. Para obter mais informações,
consulte componentes ASP.NET CoreRazor.

Layout de implantação do WebAssembly


Use um layout de implantação para habilitar Blazor WebAssembly downloads de
aplicativos em ambientes de segurança restritos. Para obter mais informações, consulte
Layout de implantação para aplicativos ASP.NET CoreBlazor WebAssembly.

Novos Blazor artigos


Além dos Blazor recursos descritos nas seções anteriores, novos Blazor artigos estão
disponíveis sobre os seguintes assuntos:

Blazor ASP.NET Core downloads de arquivo: saiba como baixar um arquivo usando
a interoperabilidade de streaming nativa byte[] para garantir uma transferência
eficiente para o cliente.
Trabalhar com imagens no ASP.NET CoreBlazor: descubra como trabalhar com
imagens em Blazor aplicativos, incluindo como transmitir dados de imagem e
visualizar uma imagem.

Criar Blazor Hybrid aplicativos com .NET MAUI,


WPF e Windows Forms
Use Blazor Hybrid para combinar estruturas de cliente nativas da área de trabalho e
móveis com .NET e Blazor:

.NET Multi-platform App UI (.NET MAUI) é uma estrutura multiplataforma para


criar aplicativos móveis e de área de trabalho nativos com C# e XAML.
Blazor Hybridos aplicativos podem ser criados com estruturas de Windows
Presentation Foundation (WPF) e Windows Forms.

) Importante

Blazor Hybrid está em versão prévia e não deve ser usado em aplicativos de
produção até o lançamento final.

Para saber mais, consulte os recursos a seguir:


Documentação da versão prévia do ASP.NET Core Blazor Hybrid
O que é o .NET MAUI?
Blog do Microsoft .NET (categoria: ".NET MAUI")

Kestrel
HTTP/3 está atualmente em rascunho e, portanto, sujeito a alterações. O suporte a
HTTP/3 no ASP.NET Core não é lançado, é um recurso de visualização incluído no .NET
6.

Kestrel agora dá suporte a HTTP/3. Para obter mais informações, consulte Usar HTTP/3
com o servidor Web ASP.NET Core Kestrel e o suporte à entrada de blog HTTP/3 no
.NET 6 .

Novas Kestrel categorias de log para registro em log


selecionado
Antes dessa alteração, habilitar o log detalhado para Kestrel era proibitivamente caro,
pois todo o Microsoft.AspNetCore.Server.Kestrel nome da categoria de Kestrel registro
em log compartilhado. Microsoft.AspNetCore.Server.Kestrel ainda está disponível, mas
as novas subcategorias a seguir permitem mais controle do registro em log:

Microsoft.AspNetCore.Server.Kestrel (categoria atual): ApplicationError , ,

ConnectionHeadResponseBodyWrite , ApplicationNeverCompleted , RequestBodyStart ,


RequestBodyDone , RequestBodyNotEntirelyRead , RequestBodyDrainTimedOut ,

ResponseMinimumDataRateNotSatisfied , InvalidResponseHeaderRemoved ,
HeartbeatSlow .

Microsoft.AspNetCore.Server.Kestrel.BadRequests : ConnectionBadRequest ,

RequestProcessingError , RequestBodyMinimumDataRateNotSatisfied .
Microsoft.AspNetCore.Server.Kestrel.Connections : ConnectionAccepted ,

ConnectionStart , ConnectionStop , ConnectionPause , ConnectionResume ,


ConnectionKeepAlive , ConnectionRejected , ConnectionDisconnect ,

NotAllConnectionsClosedGracefully , NotAllConnectionsAborted ,
ApplicationAbortedConnection .

Microsoft.AspNetCore.Server.Kestrel.Http2 : Http2ConnectionError ,

Http2ConnectionClosing , Http2ConnectionClosed , Http2StreamError ,


Http2StreamResetAbort , HPackDecodingError , HPackEncodingError ,

Http2FrameReceived , Http2FrameSending , Http2MaxConcurrentStreamsReached .


Microsoft.AspNetCore.Server.Kestrel.Http3 : Http3ConnectionError ,

Http3ConnectionClosing , Http3ConnectionClosed , Http3StreamAbort ,


Http3FrameReceived , Http3FrameSending .

As regras existentes continuam funcionando, mas agora você pode ser mais seletivo em
quais regras você habilita. Por exemplo, a sobrecarga de observabilidade de habilitar
Debug o registro em log apenas para solicitações incorretas é muito reduzida e pode ser

habilitada com a seguinte configuração:

XML

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.Kestrel.BadRequests": "Debug"
}
}

A filtragem de log aplica regras com o prefixo de categoria correspondente mais longo.
Para obter mais informações, consulte Como as regras de filtragem são aplicadas

KestrelEmitir ServerOptions por meio do evento


EventSource
O KestrelEventSource emite um novo evento que contém o JSon-serialized
KestrelServerOptions quando habilitado com detalhamento EventLevel.LogAlways . Esse
evento facilita o raciocínio sobre o comportamento do servidor ao analisar
rastreamentos coletados. O seguinte JSON é um exemplo do conteúdo do evento:

JSON

{
"AllowSynchronousIO": false,
"AddServerHeader": true,
"AllowAlternateSchemes": false,
"AllowResponseHeaderCompression": true,
"EnableAltSvc": false,
"IsDevCertLoaded": true,
"RequestHeaderEncodingSelector": "default",
"ResponseHeaderEncodingSelector": "default",
"Limits": {
"KeepAliveTimeout": "00:02:10",
"MaxConcurrentConnections": null,
"MaxConcurrentUpgradedConnections": null,
"MaxRequestBodySize": 30000000,
"MaxRequestBufferSize": 1048576,
"MaxRequestHeaderCount": 100,
"MaxRequestHeadersTotalSize": 32768,
"MaxRequestLineSize": 8192,
"MaxResponseBufferSize": 65536,
"MinRequestBodyDataRate": "Bytes per second: 240, Grace Period:
00:00:05",
"MinResponseDataRate": "Bytes per second: 240, Grace Period: 00:00:05",
"RequestHeadersTimeout": "00:00:30",
"Http2": {
"MaxStreamsPerConnection": 100,
"HeaderTableSize": 4096,
"MaxFrameSize": 16384,
"MaxRequestHeaderFieldSize": 16384,
"InitialConnectionWindowSize": 131072,
"InitialStreamWindowSize": 98304,
"KeepAlivePingDelay": "10675199.02:48:05.4775807",
"KeepAlivePingTimeout": "00:00:20"
},
"Http3": {
"HeaderTableSize": 0,
"MaxRequestHeaderFieldSize": 16384
}
},
"ListenOptions": [
{
"Address": "https://127.0.0.1:7030",
"IsTls": true,
"Protocols": "Http1AndHttp2"
},
{
"Address": "https://[::1]:7030",
"IsTls": true,
"Protocols": "Http1AndHttp2"
},
{
"Address": "http://127.0.0.1:5030",
"IsTls": false,
"Protocols": "Http1AndHttp2"
},
{
"Address": "http://[::1]:5030",
"IsTls": false,
"Protocols": "Http1AndHttp2"
}
]
}

Novo evento DiagnosticSource para solicitações HTTP


rejeitadas
Kestrel agora emite um novo DiagnosticSource evento para solicitações HTTP rejeitadas
na camada de servidor. Antes dessa alteração, não havia como observar essas
solicitações rejeitadas. O novo DiagnosticSource evento
Microsoft.AspNetCore.Server.Kestrel.BadRequest contém um
IBadRequestExceptionFeature que pode ser usado para introspectar o motivo para
rejeitar a solicitação.

C#

using Microsoft.AspNetCore.Http.Features;
using System.Diagnostics;

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();
var diagnosticSource = app.Services.GetRequiredService<DiagnosticListener>
();
using var badRequestListener = new BadRequestEventListener(diagnosticSource,
(badRequestExceptionFeature) =>
{
app.Logger.LogError(badRequestExceptionFeature.Error, "Bad request
received");
});
app.MapGet("/", () => "Hello world");

app.Run();

class BadRequestEventListener : IObserver<KeyValuePair<string, object>>,


IDisposable
{
private readonly IDisposable _subscription;
private readonly Action<IBadRequestExceptionFeature> _callback;

public BadRequestEventListener(DiagnosticListener diagnosticListener,


Action<IBadRequestExceptionFeature>
callback)
{
_subscription = diagnosticListener.Subscribe(this!, IsEnabled);
_callback = callback;
}
private static readonly Predicate<string> IsEnabled = (provider) =>
provider switch
{
"Microsoft.AspNetCore.Server.Kestrel.BadRequest" => true,
_ => false
};
public void OnNext(KeyValuePair<string, object> pair)
{
if (pair.Value is IFeatureCollection featureCollection)
{
var badRequestFeature =
featureCollection.Get<IBadRequestExceptionFeature>();
if (badRequestFeature is not null)
{
_callback(badRequestFeature);
}
}
}
public void OnError(Exception error) { }
public void OnCompleted() { }
public virtual void Dispose() => _subscription.Dispose();
}

Para obter mais informações, confira Registro em log e diagnóstico no Kestrel.

Criar um ConnectionContext a partir de um Soquete de


Aceitação
O novo SocketConnectionContextFactory possibilita criar um ConnectionContext de um
soquete aceito. Isso possibilita criar um soquete personalizado baseado sem
IConnectionListenerFactory perder todo o trabalho de desempenho e o pooling que
acontecem no SocketConnection .

Veja este exemplo de um IConnectionListenerFactory personalizado que mostra como


usar esse SocketConnectionContextFactory .

Kestrel é o perfil de inicialização padrão do Visual Studio


O perfil de inicialização padrão para todos os novos projetos Web dotnet é Kestrel. A
inicialização Kestrel é significativamente mais rápida e resulta em uma experiência mais
responsiva ao desenvolver aplicativos.

IIS Express ainda está disponível como um perfil de inicialização para cenários como
Autenticação do Windows ou compartilhamento de porta.

As portas localhost para Kestrel são aleatórias


Consulte Portas geradas por modelo para Kestrel neste documento para obter mais
informações.

Autenticação e autorização

Servidores de autenticação
O .NET 3 para o .NET 5 usou Identityo Server4 como parte do nosso modelo para dar
suporte à emissão de tokens JWT para SPA e Blazor aplicativos. Os modelos agora usam
o Servidor DuendeIdentity .

Se você estiver estendendo os modelos de identidade e estiver atualizando projetos


existentes, precisará atualizar os namespaces em seu código de para
Duende.IdentityServer e seguir as instruções de

IdentityServer4.IdentityServer migração .

O modelo de licença do Duende Identity Server foi alterado para uma licença recíproca,
o que pode exigir taxas de licença quando ele é usado comercialmente em produção.
Confira a página Licença do Duende para obter mais detalhes.

Negociação de certificado de cliente atrasada


Os desenvolvedores agora podem aceitar usar a negociação de certificado de cliente
atrasada especificando ClientCertificateMode.DelayCertificate no
HttpsConnectionAdapterOptions. Isso só funciona com conexões HTTP/1.1 porque
HTTP/2 proíbe a renegociação de certificados atrasadas. O chamador dessa API deve
armazenar o corpo da solicitação em buffer antes de solicitar o certificado do cliente:

C#

using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.WebUtilities;

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.UseKestrel(options =>
{
options.ConfigureHttpsDefaults(adapterOptions =>
{
adapterOptions.ClientCertificateMode =
ClientCertificateMode.DelayCertificate;
});
});

var app = builder.Build();


app.Use(async (context, next) =>
{
bool desiredState = GetDesiredState();
// Check if your desired criteria is met
if (desiredState)
{
// Buffer the request body
context.Request.EnableBuffering();
var body = context.Request.Body;
await body.DrainAsync(context.RequestAborted);
body.Position = 0;
// Request client certificate
var cert = await context.Connection.GetClientCertificateAsync();

// Disable buffering on future requests if the client doesn't


provide a cert
}
await next(context);
});

app.MapGet("/", () => "Hello World!");


app.Run();

OnCheckSlidingExpiration evento para controlar a cookie


renovação
Cookie A expiração deslizante de autenticação agora pode ser personalizada ou
suprimida usando o novo OnCheckSlidingExpiration. Por exemplo, esse evento pode ser
usado por um aplicativo de página única que precisa executar ping periodicamente no
servidor sem afetar a sessão de autenticação.

Diversos

Hot Reload
Faça rapidamente atualizações de interface do usuário e de código para aplicativos em
execução sem perder o estado do aplicativo para uma experiência de desenvolvedor
mais rápida e produtiva usando Recarga Dinâmica. Para obter mais informações,
consulte Suporte ao .NET Recarga Dinâmica para ASP.NET Core e Atualização no
progresso do .NET Recarga Dinâmica e Realces do Visual Studio 2022 .

Modelos de SPA (aplicativo de página única) aprimorados


Os modelos de projeto ASP.NET Core foram atualizados para Angular e React usar um
padrão aprimorado para aplicativos de página única mais flexíveis e mais alinhados com
padrões comuns para o desenvolvimento web de front-end moderno.

Anteriormente, o modelo de ASP.NET Core para Angular e React usava middleware


especializado durante o desenvolvimento para iniciar o servidor de desenvolvimento
para a estrutura de front-end e, em seguida, solicitações de proxy de ASP.NET Core para
o servidor de desenvolvimento. A lógica para iniciar o servidor de desenvolvimento de
front-end era específica para a interface de linha de comando para a estrutura de front-
end correspondente. Dar suporte a estruturas front-end adicionais usando esse padrão
significava adicionar lógica adicional a ASP.NET Core.

Os modelos de ASP.NET Core atualizados para Angular e React no .NET 6 invertem essa
disposição e aproveitam o suporte interno a proxying nos servidores de
desenvolvimento da maioria das estruturas de front-end modernas. Quando o aplicativo
ASP.NET Core é iniciado, o servidor de desenvolvimento de front-end é iniciado
exatamente como antes, mas o servidor de desenvolvimento é configurado para
solicitações de proxy para o processo de ASP.NET Core de back-end. Toda a
configuração específica de front-end para configurar o proxy faz parte do aplicativo,
não ASP.NET Core. A configuração de projetos ASP.NET Core para trabalhar com outras
estruturas de front-end agora é direta: configure o servidor de desenvolvimento de
front-end para a estrutura escolhida para proxy para o back-end ASP.NET Core usando
o padrão estabelecido nos modelos de Angular e React.

O código de inicialização do aplicativo ASP.NET Core não precisa mais de nenhuma


lógica específica do aplicativo de página única. A lógica para iniciar o servidor de
desenvolvimento front-end durante o desenvolvimento é injetar no aplicativo em
runtime pelo novo pacote Microsoft.AspNetCore.SpaProxy . O roteamento de fallback
é tratado usando o roteamento de ponto de extremidade em vez do middleware
específico do SPA.

Os modelos que seguem esse padrão ainda podem ser executados como um único
projeto no Visual Studio ou usando dotnet run a partir da linha de comando. Quando o
aplicativo é publicado, o código de front-end na pasta ClientApp é criado e coletado
como antes na raiz da Web do host ASP.NET Core aplicativo e servido como arquivos
estáticos. Os scripts incluídos no modelo configuram o servidor de desenvolvimento de
front-end para usar HTTPS usando o certificado de desenvolvimento ASP.NET Core.

Rascunho de suporte a HTTP/3 no .NET 6


HTTP/3 está atualmente em rascunho e, portanto, sujeito a alterações. O suporte a
HTTP/3 no ASP.NET Core não é lançado, é um recurso de visualização incluído no .NET
6.

Confira o suporte à entrada de blog HTTP/3 no .NET 6 .

Anotações de tipo de referência anuláveis


Partes do código-fonte ASP.NET Core 6.0 tiveram anotações de nulidade aplicadas.
Ao utilizar o novo recurso anulável no C# 8, ASP.NET Core pode fornecer segurança em
tempo de compilação adicional na manipulação de tipos de referência. Por exemplo,
proteger contra null exceções de referência. Os projetos que optaram por usar
anotações anuláveis podem ver novos avisos de tempo de compilação de APIs de
ASP.NET Core.

Para habilitar tipos de referência anuláveis, adicione a seguinte propriedade aos


arquivos de projeto:

XML

<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>

Para obter mais informações, confira Tipos de referência anuláveis.

Análise de código-fonte
Vários analisadores de plataforma do compilador .NET foram adicionados que
inspecionam o código do aplicativo em busca de problemas como configuração ou
ordem incorreta de middleware, conflitos de roteamento etc. Para obter mais
informações, consulte Análise de código em aplicativos ASP.NET Core.

Aprimoramentos do modelo de aplicativo Web


Os modelos de aplicativo Web:

Use o novo modelo de hospedagem mínima.


Reduz significativamente o número de arquivos e linhas de código necessários
para criar um aplicativo. Por exemplo, o ASP.NET Core aplicativo Web vazio cria um
arquivo C# com quatro linhas de código e é um aplicativo completo.
Unifica Startup.cs e Program.cs em um único Program.cs arquivo.
Usa instruções de nível superior para minimizar o código necessário para um
aplicativo.
Usa diretivas globais using para eliminar ou minimizar o número de linhas de using
instrução necessárias.

Portas geradas por modelo para Kestrel


Portas aleatórias são atribuídas durante a criação do projeto para uso pelo Kestrel
servidor Web. Portas aleatórias ajudam a minimizar um conflito de porta quando vários
projetos são executados no mesmo computador.

Quando um projeto é criado, uma porta HTTP aleatória entre 5000-5300 e uma porta
HTTPS aleatória entre 7000-7300 é especificada no arquivo gerado
Properties/launchSettings.json . As portas podem ser alteradas no
Properties/launchSettings.json arquivo. Se nenhuma porta for especificada, Kestrel o

padrão será as portas HTTP 5000 e HTTPS 5001. Para obter mais informações, consulte
Configurar pontos de extremidade para o servidor Web ASP.NET CoreKestrel.

Novos padrões de registro em log


As seguintes alterações foram feitas em e
appsettings.json appsettings.Development.json :

diff

- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"

A alteração de "Microsoft": "Warning" para "Microsoft.AspNetCore": "Warning" resulta


no registro em log de todas as mensagens informativas do Microsoft namespace ,
exceto Microsoft.AspNetCore . Por exemplo, Microsoft.EntityFrameworkCore agora está
registrado no nível informativo.

Página de exceção do desenvolvedor Middleware


adicionada automaticamente
No ambiente de desenvolvimento, o DeveloperExceptionPageMiddleware é adicionado
por padrão. Não é mais necessário adicionar o seguinte código aos aplicativos de
interface do usuário da Web:

C#

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

Suporte para cabeçalhos de solicitação codificados em


Latin1 no HttpSysServer
HttpSysServer agora dá suporte à decodificação de cabeçalhos de solicitação

codificados Latin1 definindo a UseLatin1RequestHeaders propriedade como


HttpSysOptions true :

C#

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.UseHttpSys(o => o.UseLatin1RequestHeaders = true);

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Os logs do Módulo ASP.NET Core incluem carimbos de


data/hora e PID
Os logs de diagnóstico aprimorados do ANCM (Módulo ASP.NET Core) para IIS (ANCM)
incluem carimbos de data/hora e PID do processo que emite os logs. Registrar
carimbos de data/hora e PID facilita o diagnóstico de problemas com reinicializações de
processo sobrepostas no IIS quando vários processos de trabalho do IIS estão em
execução.

Os logs resultantes agora se assemelham ao exemplo de saída mostrado abaixo:

CLI do .NET

[2021-07-28T19:23:44.076Z, PID: 11020] [aspnetcorev2.dll] Initializing logs


for 'C:\<path>\aspnetcorev2.dll'. Process Id: 11020. File Version:
16.0.21209.0. Description: IIS ASP.NET Core Module V2. Commit:
96475a2acdf50d7599ba8e96583fa73efbe27912.
[2021-07-28T19:23:44.079Z, PID: 11020] [aspnetcorev2.dll] Resolving hostfxr
parameters for application: '.\InProcessWebSite.exe' arguments: '' path:
'C:\Temp\e86ac4e9ced24bb6bacf1a9415e70753\'
[2021-07-28T19:23:44.080Z, PID: 11020] [aspnetcorev2.dll] Known dotnet.exe
location: ''

Tamanho do buffer de entrada inconsumável configurável


para o IIS
O servidor IIS anteriormente armazenava em buffer apenas 64 KiB de corpos de
solicitação não armazenados em buffer. O buffer de 64 KiB resultou em leituras restritas
a esse tamanho máximo, o que afeta o desempenho com grandes corpos de entrada,
como uploads. No .NET 6, o tamanho do buffer padrão muda de 64 KiB para 1 MiB, o
que deve melhorar a taxa de transferência para carregamentos grandes. Em nossos
testes, um upload de 700 MiB que costumava levar 9 segundos agora leva apenas 2,5
segundos.

A desvantagem de um tamanho de buffer maior é um aumento no consumo de


memória por solicitação quando o aplicativo não está lendo rapidamente do corpo da
solicitação. Portanto, além de alterar o tamanho do buffer padrão, o tamanho do buffer
configurável, permitindo que os aplicativos configurem o tamanho do buffer com base
na carga de trabalho.

Exibir auxiliares de marcação de componentes


Considere um componente de exibição com um parâmetro opcional, conforme
mostrado no seguinte código:

C#

class MyViewComponent
{
IViewComponentResult Invoke(bool showSomething = false) { ... }
}

Com ASP.NET Core 6, o auxiliar de marca pode ser invocado sem precisar especificar um
valor para o showSomething parâmetro :

razor

<vc:my />

Angular modelo atualizado para Angular 12


O modelo ASP.NET Core 6.0 para Angular agora usa Angular 12 .

O modelo React foi atualizado para React 17 .

Limite de buffer configurável antes de gravar em disco no


formatador de saída Json.NET
Observação: recomendamos usar o System.Text.Json formatador de saída, exceto
quando o Newtonsoft.Json serializador é necessário por motivos de compatibilidade. O
System.Text.Json serializador é totalmente async e funciona com eficiência para cargas

maiores.

O Newtonsoft.Json formatador de saída por padrão armazena respostas de até 32 KiB


na memória antes de fazer buffer no disco. Isso é para evitar a execução de E/S síncrona,
o que pode resultar em outros efeitos colaterais, como a falta de threads e deadlocks de
aplicativo. No entanto, se a resposta for maior que 32 KiB, ocorrerá uma E/S de disco
considerável. O limite de memória agora é configurável por meio da propriedade
MvcNewtonsoftJsonOptions.OutputFormatterMemoryBufferThreshold antes de fazer
buffer no disco:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages()
.AddNewtonsoftJson(options =>
{
options.OutputFormatterMemoryBufferThreshold = 48 * 1024;
});

var app = builder.Build();

Para obter mais informações, consulte esta solicitação de pull do GitHub e o arquivo
NewtonsoftJsonOutputFormatterTest.cs .

Obter e definir mais rapidamente para cabeçalhos HTTP


Novas APIs foram adicionadas para expor todos os cabeçalhos comuns disponíveis em
Microsoft.Net.Http.Headers.HeaderNames como propriedades, resultando
IHeaderDictionary em uma API mais fácil de usar. Por exemplo, o middleware em linha
no código a seguir obtém e define cabeçalhos de solicitação e resposta usando as
novas APIs:

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Use(async (context, next) =>


{
var hostHeader = context.Request.Headers.Host;
app.Logger.LogInformation("Host header: {host}", hostHeader);
context.Response.Headers.XPoweredBy = "ASP.NET Core 6.0";
await next.Invoke(context);
var dateHeader = context.Response.Headers.Date;
app.Logger.LogInformation("Response date: {date}", dateHeader);
});

app.Run();

Para cabeçalhos implementados, os acessadores get e set são implementados


acessando diretamente o campo e ignorando a pesquisa. Para cabeçalhos não
implementados, os acessadores podem ignorar a pesquisa inicial em relação aos
cabeçalhos implementados e executar diretamente a Dictionary<string, StringValues>
pesquisa. Evitar a pesquisa resulta em acesso mais rápido para ambos os cenários.

Streaming assíncrono
ASP.NET Core agora dá suporte ao streaming assíncrono de ações e respostas do
controlador do JS Formatador ON. Retornar um IAsyncEnumerable de uma ação não
armazena mais em buffer o conteúdo da resposta na memória antes de ser enviado. O
não buffer ajuda a reduzir o uso de memória ao retornar grandes conjuntos de dados
que podem ser enumerados de forma assíncrona.

Observe que o Entity Framework Core fornece implementações de IAsyncEnumerable


para consultar o banco de dados. O suporte aprimorado para IAsyncEnumerable em
ASP.NET Core no .NET 6 pode tornar o uso EF Core com ASP.NET Core mais eficiente.
Por exemplo, o código a seguir não armazena mais os dados do produto na memória
antes de enviar a resposta:

C#

public IActionResult GetMovies()


{
return Ok(_context.Movie);
}

No entanto, ao usar o carregamento lento no EF Core, esse novo comportamento pode


resultar em erros devido à execução simultânea da consulta enquanto os dados estão
sendo enumerados. Os aplicativos podem reverter para o comportamento anterior
armazenando os dados em buffer:

C#

public async Task<IActionResult> GetMovies2()


{
return Ok(await _context.Movie.ToListAsync());
}

Consulte o comunicado relacionado para obter detalhes adicionais sobre essa


alteração no comportamento.

Middleware de log HTTP


O log HTTP é um novo middleware interno que registra informações sobre solicitações
HTTP e respostas HTTP, incluindo os cabeçalhos e todo o corpo:

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();


app.UseHttpLogging();

app.MapGet("/", () => "Hello World!");

app.Run();

Navegando até / com as informações de logs de código anteriores semelhantes à


seguinte saída:

CLI do .NET

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/2
Method: GET
Scheme: https
PathBase:
Path: /
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,
*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cache-Control: max-age=0
Connection: close
Cookie: [Redacted]
Host: localhost:44372
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Edg/95.0.1020.30
sec-ch-ua: [Redacted]
sec-ch-ua-mobile: [Redacted]
sec-ch-ua-platform: [Redacted]
upgrade-insecure-requests: [Redacted]
sec-fetch-site: [Redacted]
sec-fetch-mode: [Redacted]
sec-fetch-user: [Redacted]
sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: text/plain; charset=utf-8

A saída anterior foi habilitada com o seguinte appsettings.Development.json arquivo:

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware":
"Information"
}
}
}

O registro em log HTTP fornece logs de:

Informações de solicitação HTTP


Propriedades comuns
Cabeçalhos
Corpo
Informações de resposta HTTP

Para configurar o middleware de log HTTP, especifique HttpLoggingOptions:

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddHttpLogging(logging =>
{
// Customize HTTP logging.
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("My-Request-Header");
logging.ResponseHeaders.Add("My-Response-Header");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
var app = builder.Build();
app.UseHttpLogging();

app.MapGet("/", () => "Hello World!");

app.Run();

IConnectionSocketFeature
O IConnectionSocketFeature recurso de solicitação fornece acesso ao soquete de
aceitação subjacente associado à solicitação atual. Ele pode ser acessado por meio do
FeatureCollection em HttpContext .

Por exemplo, o aplicativo a seguir define a LingerState propriedade no soquete aceito:

C#

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureEndpointDefaults(listenOptions =>
listenOptions.Use((connection, next) =>
{
var socketFeature =
connection.Features.Get<IConnectionSocketFeature>();
socketFeature.Socket.LingerState = new LingerOption(true, seconds:
10);
return next();
}));
});
var app = builder.Build();
app.MapGet("/", (Func<string>)(() => "Hello world"));
await app.RunAsync();

Restrições de tipo genérico em Razor


Ao definir parâmetros de tipo genérico no Razor uso da diretiva , restrições @typeparam
de tipo genérico agora podem ser especificadas usando a sintaxe padrão do C#:

SignalRScripts menores , Blazor Servere MessagePack


Os SignalRscripts , MessagePack e Blazor Server agora são significativamente menores,
permitindo downloads menores, menos análise e compilação de JavaScript pelo
navegador e inicialização mais rápida. As reduções de tamanho:
signalr.js : 70%

blazor.server.js : 45%

Os scripts menores são resultado de uma contribuição da comunidade de Ben Adams .


Para obter mais informações sobre os detalhes da redução de tamanho, consulte
Solicitação de pull do GitHub de Ben .

Habilitar sessões de criação de perfil do Redis


Uma contribuição da comunidade de Gabriel Lucaci habilita a sessão de criação de
perfil do Redis com Microsoft.Extensions.Caching.StackExchangeRedis :

C#

using StackExchange.Redis.Profiling;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddStackExchangeRedisCache(options =>
{
options.ProfilingSession = () => new ProfilingSession();
});

Para obter mais informações, consulte Criação de perfil do StackExchange.Redis .

Cópia de sombra no IIS


Um recurso experimental foi adicionado ao ANCM (Módulo ASP.NET Core) para IIS para
adicionar suporte para assemblies de aplicativo de cópia de sombra. Atualmente, o .NET
bloqueia binários de aplicativos ao ser executado no Windows, impossibilitando a
substituição de binários quando o aplicativo está em execução. Embora nossa
recomendação permaneça para usar um arquivo offline do aplicativo, reconhecemos
que há determinados cenários (por exemplo, implantações ftp) em que não é possível
fazê-lo.

Nesses cenários, habilite a cópia de sombra personalizando as configurações do


manipulador de módulo ASP.NET Core. Na maioria dos casos, ASP.NET Core aplicativos
não têm um web.config controle do código-fonte verificado que você pode modificar.
Em ASP.NET Core, web.config normalmente é gerado pelo SDK. O exemplo web.config
a seguir pode ser usado para começar:

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- To customize the asp.net core module uncomment and edit the following
section.
For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->

<system.webServer>
<handlers>
<remove name="aspNetCore"/>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2"
resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%"
stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout">
<handlerSettings>
<handlerSetting name="experimentalEnableShadowCopy" value="true" />
<handlerSetting name="shadowCopyDirectory"
value="../ShadowCopyDirectory/" />
<!-- Only enable handler logging if you encounter issues-->
<!--<handlerSetting name="debugFile" value=".\logs\aspnetcore-
debug.log" />-->
<!--<handlerSetting name="debugLevel" value="FILE,TRACE" />-->
</handlerSettings>
</aspNetCore>
</system.webServer>
</configuration>

A cópia de sombra no IIS é um recurso experimental que não tem garantia de fazer
parte do ASP.NET Core. Deixe comentários sobre a cópia de Sombra do IIS neste
problema do GitHub .

Recursos adicionais
Exemplos de código migrados para o novo modelo de hospedagem mínimo na
versão 6.0
Novidades no .NET 6
Novidades no ASP.NET Core 5.0
Artigo • 05/10/2022 • 16 minutos para o fim da leitura

Este artigo destaca as alterações mais significativas no ASP.NET Core 5.0 com links para
a documentação relevante.

ASP.NET Core MVC e Razor melhorias

DateTime de associação de modelo como UTC


A associação de modelo agora dá suporte à associação de cadeias de caracteres de
tempo UTC para DateTime . Se a solicitação contiver uma cadeia de caracteres de tempo
UTC, a associação de modelo a associará a um UTC DateTime . Por exemplo, a cadeia de
caracteres de tempo a seguir está vinculada ao UTC DateTime :
https://example.com/mycontroller/myaction?time=2019-06-14T02%3A30%3A04.0576719Z

Associação e validação de modelo com tipos de registro


C# 9
Os tipos de registro C# 9 podem ser usados com a associação de modelo em um
controlador MVC ou uma Razor página. Os tipos de registro são uma boa maneira de
modelar dados que estão sendo transmitidos pela rede.

Por exemplo, o seguinte PersonController usa o tipo de Person registro com


associação de modelo e validação de formulário:

C#

public record Person([Required] string Name, [Range(0, 150)] int Age);

public class PersonController


{
public IActionResult Index() => View();

[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}

O arquivo Person/Index.cshtml :
CSHTML

@model Person

Name: <input asp-for="Model.Name" />


<span asp-validation-for="Model.Name" />

Age: <input asp-for="Model.Age" />


<span asp-validation-for="Model.Age" />

Melhorias no DynamicRouteValueTransformer
ASP.NET Core 3.1 introduzido DynamicRouteValueTransformer como uma forma de usar
o ponto de extremidade personalizado para selecionar dinamicamente uma ação do
controlador MVC ou uma Razor página. ASP.NET Core aplicativos 5.0 podem passar o
estado para um DynamicRouteValueTransformer e filtrar o conjunto de pontos de
extremidade escolhidos.

Diversos
O atributo [Compare] pode ser aplicado a propriedades em um Razor modelo de
página.
Parâmetros e propriedades associadas do corpo são considerados exigidos por
padrão.

API Web

Especificação de OpenAPI por padrão


A Especificação openAPI é um padrão do setor para descrever APIs HTTP e integrá-las
a processos de negócios complexos ou a terceiros. O OpenAPI é amplamente
compatível com todos os provedores de nuvem e muitos registros de API. Aplicativos
que emitem documentos OpenAPI de APIs Web têm uma variedade de novas
oportunidades nas quais essas APIs podem ser usadas. Em parceria com os
mantenedores do projeto de software livre Swashbuckle.AspNetCore , o modelo de
API ASP.NET Core contém uma dependência do NuGet no Swashbuckle . O
Swashbuckle é um pacote NuGet de software livre popular que emite documentos
OpenAPI dinamicamente. O Swashbuckle faz isso introspecndo sobre os controladores
de API e gerando o documento OpenAPI em tempo de execução ou em tempo de build
usando a CLI do Swashbuckle.
No ASP.NET Core 5.0, os modelos de API Web habilitam o suporte a OpenAPI por
padrão. Para desabilitar o OpenAPI:

Na linha de comando:

CLI do .NET

dotnet new webapi --no-openapi true

No Visual Studio: desmarque habilitar o suporte ao OpenAPI.

Todos os .csproj arquivos criados para projetos de API Web contêm a referência do
pacote NuGet Swashbuckle.AspNetCore .

XML

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
</ItemGroup>

O código gerado pelo modelo contém código que Startup.ConfigureServices ativa a


geração de documentos OpenAPI:

C#

public void ConfigureServices(IServiceCollection services)


{

services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApp1", Version =
"v1" });
});
}

O Startup.Configure método adiciona o middleware Swashbuckle, que permite:

Processo de geração de documentos.


Página de interface do usuário do Swagger por padrão no modo de
desenvolvimento.

O código gerado pelo modelo não exporá acidentalmente a descrição da API ao


publicar em produção.

C#
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
"WebApp1 v1"));
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Importação de Gerenciamento de API do Azure


Quando ASP.NET Core projetos de API habilitam o OpenAPI, o Visual Studio 2019 versão
16.8 e a publicação posterior oferecem automaticamente uma etapa adicional no fluxo
de publicação. Os desenvolvedores que usam o Azure Gerenciamento de API têm a
oportunidade de importar automaticamente as APIs para o Azure Gerenciamento de API
durante o fluxo de publicação:
Melhor experiência de inicialização para projetos de API
Web
Com o OpenAPI habilitado por padrão, a experiência de inicialização do aplicativo (F5)
para desenvolvedores de API Web melhora significativamente. Com ASP.NET Core 5.0, o
modelo de API Web vem pré-configurado para carregar a página de interface do
usuário do Swagger. A página de interface do usuário do Swagger fornece a
documentação adicionada para a API publicada e permite testar as APIs com um único
clique.
Blazor

Aprimoramentos de desempenho
Para o .NET 5, fizemos melhorias significativas Blazor WebAssembly no desempenho do
runtime com um foco específico na renderização de interface do usuário complexa e
JSna serialização ON. Em nossos testes de desempenho, Blazor WebAssembly no .NET 5
é duas a três vezes mais rápido para a maioria dos cenários. Para obter mais
informações, consulte ASP.NET Blog: ASP.NET Core atualizações no .NET 5 Release
Candidate 1 .

Isolamento de CSS
Blazor agora dá suporte à definição de estilos CSS que têm como escopo um
determinado componente. Estilos CSS específicos do componente facilitam o raciocínio
sobre os estilos em um aplicativo e evitar efeitos colaterais não intencionais de estilos
globais. Para obter mais informações, consulte ASP.NET Core Blazor isolamento do CSS.

Novo InputFile componente


O InputFile componente permite a leitura de um ou mais arquivos selecionados por
um usuário para upload. Para obter mais informações, consulte ASP.NET Core Blazor
uploads de arquivo.
Novos InputRadio e InputRadioGroup componentes
Blazor InputRadio tem componentes internos e InputRadioGroup que simplificam a
associação de dados a grupos de botões de opção com validação integrada. Para obter
mais informações, consulte ASP.NET Core Blazor formulários e componentes de entrada.

Virtualização de componente
Melhore o desempenho percebido da renderização de componentes usando o Blazor
suporte interno de virtualização da estrutura. Para obter mais informações, consulte
ASP.NET Core Razor virtualização do componente.

ontoggle suporte a eventos

Blazor os eventos agora dão suporte ao ontoggle evento DOM. Para obter mais
informações, consulte ASP.NET Core Blazor tratamento de eventos.

Definir o foco da interface do usuário em Blazor


aplicativos
Use o FocusAsync método de conveniência em referências de elemento para definir o
foco da interface do usuário para esse elemento. Para obter mais informações, consulte
ASP.NET Core Blazor tratamento de eventos.

Atributos de classe CSS de validação personalizada


Atributos de classe CSS de validação personalizada são úteis ao integrar-se a estruturas
CSS, como Bootstrap. Para obter mais informações, consulte ASP.NET Core Blazor
formulários e componentes de entrada.

Suporte a IAsyncDisposable
Razor os componentes agora dão suporte à IAsyncDisposable interface para a versão
assíncrona de recursos alocados.

Referências de objeto e isolamento do JavaScript


Blazor habilita o isolamento javaScript em módulos JavaScript padrão. Para obter mais
informações, consulte Chamar funções JavaScript de métodos .NET no ASP.NET Core
Blazor.
Os componentes de formulário dão suporte ao nome de
exibição
Os seguintes componentes internos dão suporte a nomes de exibição com o
DisplayName parâmetro:

InputDate

InputNumber
InputSelect

Para obter mais informações, consulte ASP.NET Core Blazor formulários e componentes
de entrada.

Parâmetros de rota catch-all


Os parâmetros de rota catch-all, que capturam caminhos entre vários limites de pasta,
têm suporte em componentes. Para obter mais informações, consulte ASP.NET Core
Blazor roteamento e navegação.

Melhorias na depuração
A depuração de Blazor WebAssembly aplicativos é aprimorada no ASP.NET Core 5.0.
Além disso, agora há suporte para depuração em Visual Studio para Mac. Para obter
mais informações, consulte Depurar ASP.NET Core Blazor WebAssembly.

Microsoft Identity v2.0 e MSAL v2.0


Blazor A segurança agora usa Microsoft Identity v2.0 (Microsoft.Identity.Web e
Microsoft.Identity.Web.UI ) e MSAL v2.0. Para obter mais informações, consulte os
tópicos no nó e Identity segurançaBlazor.

Armazenamento protegido do navegador para Blazor


Server
Blazor Serveragora os aplicativos podem usar suporte interno para armazenar o estado
do aplicativo no navegador que foi protegido contra adulteração usando ASP.NET Core
proteção de dados. Os dados podem ser armazenados no armazenamento local do
navegador ou no armazenamento de sessão. Para obter mais informações, consulte
ASP.NET Core Blazor gerenciamento de estado.
Blazor WebAssembly pré-geração
A integração de componentes é aprimorada em modelos de hospedagem e Blazor
WebAssembly os aplicativos agora podem gerar saída no servidor.

Melhorias de corte/vinculação
Blazor WebAssembly executa o corte/vinculação de IL (Linguagem Intermediária)
durante um build para cortar IL desnecessária dos assemblies de saída do aplicativo.
Com a versão do ASP.NET Core 5.0, Blazor WebAssembly executa o corte aprimorado
com opções de configuração adicionais. Para obter mais informações, consulte
Configurar o Trimmer para opções de ASP.NET Core Blazor e Corte.

Analisador de compatibilidade do navegador


Blazor WebAssembly os aplicativos têm como destino a área completa da superfície da
API do .NET, mas nem todas as APIs do .NET têm suporte no WebAssembly devido a
restrições de área restrita do navegador. APIs sem suporte são lançadas
PlatformNotSupportedException ao serem executadas no WebAssembly. Um analisador
de compatibilidade de plataforma avisa o desenvolvedor quando o aplicativo usa APIs
que não têm suporte nas plataformas de destino do aplicativo. Para obter mais
informações, consulte Consumir componentes Razor do ASP.NET Core de uma RCL
(biblioteca de classes) Razor.

Assemblies de carga lentos


Blazor WebAssembly O desempenho de inicialização do aplicativo pode ser aprimorado
adiando o carregamento de alguns assemblies de aplicativo até que eles sejam
necessários. Para obter mais informações, consulte assemblies de carga lentos no
ASP.NET Core Blazor WebAssembly.

Suporte atualizado à globalização


O suporte à globalização está disponível para Blazor WebAssembly base em
Componentes Internacionais para Unicode (UTI). Para obter mais informações, consulte
ASP.NET Core Blazor globalização e localização.

gRPC
Muitas melhorias de pré-formação foram feitas no gRPC . Para obter mais
informações, consulte melhorias no desempenho do gRPC no .NET 5 .

Para obter mais informações sobre gRPC, consulte Visão geral do gRPC no .NET.

SignalR

SignalR Filtros de hub


SignalR Os filtros de hub, chamados de pipelines do Hub em ASP.NET SignalR, são um
recurso que permite que o código seja executado antes e depois que os métodos hub
são chamados. Executar código antes e depois que os métodos hub são chamados é
semelhante a como o middleware tem a capacidade de executar código antes e depois
de uma solicitação HTTP. Os usos comuns incluem registro em log, tratamento de erros
e validação de argumento.

Para obter mais informações, consulte Usar filtros de hub em ASP.NET Core SignalR.

SignalR invocações de hub paralelo


SignalR ASP.NET Core agora é capaz de lidar com invocações de hub paralelo. O
comportamento padrão pode ser alterado para permitir que os clientes invoquem mais
de um método hub por vez:

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddSignalR(options =>
{
options.MaximumParallelInvocationsPerClient = 5;
});
}

Adicionado suporte ao Messagepack no SignalR cliente


Java
Um novo pacote, com.microsoft.signalr.messagepack , adiciona suporte ao
MessagePack ao SignalR cliente Java. Para usar o protocolo do hub MessagePack,
adicione .withHubProtocol(new MessagePackHubProtocol()) ao construtor de conexões:

Java
HubConnection hubConnection = HubConnectionBuilder.create(
"http://localhost:53353/MyHub")
.withHubProtocol(new MessagePackHubProtocol())
.build();

Kestrel
Pontos de extremidade recarregáveis por meio da configuração: Kestrel pode
detectar alterações na configuração passadas paraKestrel ServerOptions.Configure
e desvinculada de pontos de extremidade existentes e associar a novos pontos de
extremidade sem exigir uma reinicialização do aplicativo quando o reloadOnChange
parâmetro for true . Por padrão, ao usar ConfigureWebHostDefaults ou
CreateDefaultBuilder, Kestrel associa-se à subseção de configuração "Kestrel"
com reloadOnChange habilitado. Os aplicativos devem passar reloadOnChange: true
ao chamar KestrelServerOptions.Configure manualmente para obter pontos de
extremidade recarregáveis.

Melhorias de cabeçalhos de resposta HTTP/2. Para obter mais informações,


consulte melhorias de desempenho na próxima seção.

Suporte para tipos de pontos de extremidade adicionais no transporte de


soquetes: adicionando à nova API introduzida System.Net.Sockets, o transporte
Kestrel padrão dos soquetes permite associação a identificadores de arquivo
existentes e soquetes de domínio Unix. O suporte para associação a identificadores
de arquivo existentes permite o uso da integração existente Systemd sem exigir o
libuv transporte.

Decodificação de cabeçalho personalizada em Kestrel: Os aplicativos podem


especificar quais Encoding usar para interpretar cabeçalhos de entrada com base
no nome do cabeçalho em vez de padrão para UTF-8. Defina a
Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions.RequestHeaderEncoding
Selector propriedade para especificar qual codificação usar:

C#

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.RequestHeaderEncodingSelector = encoding =>
{
return encoding switch
{
"Host" => System.Text.Encoding.Latin1,
_ => System.Text.Encoding.UTF8,
};
};
});
webBuilder.UseStartup<Startup>();
});

Kestrel opções específicas do ponto de extremidade por


meio da configuração
O suporte foi adicionado para configurar Kestrelas opções específicas do ponto de
extremidade por meio da configuração. As configurações específicas do ponto de
extremidade incluem:

Protocolos HTTP usados


Protocolos TLS usados
Certificado selecionado
Modo de certificado do cliente

A configuração permite especificar qual certificado é selecionado com base no nome do


servidor especificado. O nome do servidor faz parte da extensão SNI (Indicação de
Nome do Servidor) para o protocolo TLS, conforme indicado pelo cliente. KestrelA
configuração 's também dá suporte a um prefixo curinga no nome do host.

O exemplo a seguir mostra como especificar ponto de extremidade específico usando


um arquivo de configuração:

JSON

{
"Kestrel": {
"Endpoints": {
"EndpointName": {
"Url": "https://*",
"Sni": {
"a.example.org": {
"Protocols": "Http1AndHttp2",
"SslProtocols": [ "Tls11", "Tls12"],
"Certificate": {
"Path": "testCert.pfx",
"Password": "testPassword"
},
"ClientCertificateMode" : "NoCertificate"
},
"*.example.org": {
"Certificate": {
"Path": "testCert2.pfx",
"Password": "testPassword"
}
},
"*": {
// At least one sub-property needs to exist per
// SNI section or it cannot be discovered via
// IConfiguration
"Protocols": "Http1",
}
}
}
}
}
}

A SNI (Indicação de Nome do Servidor) é uma extensão TLS para incluir um domínio
virtual como parte da negociação de SSL. O que isso significa efetivamente é que o
nome de domínio virtual ou um nome de host pode ser usado para identificar o ponto
de extremidade de rede.

Aprimoramentos de desempenho

HTTP/2
Reduções significativas nas alocações no caminho de código HTTP/2.

Suporte para compactação dinâmica HPack de cabeçalhos de resposta HTTP/2


em Kestrel. Para obter mais informações, consulte o tamanho da tabela cabeçalho
e HPACK: o assassino silencioso (recurso) de HTTP/2 .

Enviando quadros PING HTTP/2: HTTP/2 tem um mecanismo para enviar quadros
PING para garantir que uma conexão ociosa ainda esteja funcional. Garantir uma
conexão viável é especialmente útil ao trabalhar com fluxos de longa duração que
muitas vezes estão ociosos, mas que só veem atividade intermitentemente, por
exemplo, fluxos gRPC. Os aplicativos podem enviar quadros PING periódicos
definindo limites em KestrelKestrelServerOptions:

C#

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.Limits.Http2.KeepAlivePingInterval =
TimeSpan.FromSeconds(10);
options.Limits.Http2.KeepAlivePingTimeout =
TimeSpan.FromSeconds(1);
});
webBuilder.UseStartup<Startup>();
});

Contêineres
Antes do .NET 5.0, a criação e a publicação de um Dockerfile para um aplicativo ASP.NET
Core exigiam a extração de todo o SDK do .NET Core e da imagem ASP.NET Core. Com
essa versão, a extração dos bytes de imagens do SDK é reduzida e os bytes puxados
para a imagem ASP.NET Core são em grande parte eliminados. Para obter mais
informações, consulte este comentário sobre o problema do GitHub .

Autenticação e autorização

Autenticação do Azure Active Directory com a


Microsoft.Identity. Web
Os modelos de projeto ASP.NET Core agora se integram para lidar com
Microsoft.Identity.Web a autenticação com o Azure Active Directory (Azure AD). A
Microsoft.Identity. O pacote Web fornece:

Uma experiência melhor para autenticação por meio de Azure AD.


Uma maneira mais fácil de acessar recursos do Azure em nome de seus usuários,
incluindo o Microsoft Graph. Consulte a Microsoft.Identity. Exemplo da Web , que
começa com um logon básico e avança por meio de vários locatários, usando APIs
do Azure, usando o Microsoft Graph e protegendo suas próprias APIs.
Microsoft.Identity.Web está disponível ao lado do .NET 5.

Permitir acesso anônimo a um ponto de extremidade


O AllowAnonymous método de extensão permite acesso anônimo a um ponto de
extremidade:

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
})
.AllowAnonymous();
});
}

Tratamento personalizado de falhas de autorização


O tratamento personalizado de falhas de autorização agora é mais fácil com a nova
interface IAuthorizationMiddlewareResultHandler que é invocada pelo Middleware de
autorização. A implementação padrão permanece a mesma, mas um manipulador
personalizado pode ser registrado em [Injeção de dependência, que permite respostas
HTTP personalizadas com base no motivo pelo qual a autorização falhou. Veja este
exemplo que demonstra o IAuthorizationMiddlewareResultHandler uso do .

Autorização ao usar o roteamento de ponto de


extremidade
A autorização ao usar o roteamento de ponto de extremidade agora recebe a instância
do ponto de extremidade e não a HttpContext instância do ponto de extremidade. Isso
permite que o middleware de autorização acesse as RouteData propriedades e outras
que HttpContext não estavam acessíveis por meio da Endpoint classe. O ponto de
extremidade pode ser buscado do contexto usando contexto . GetEndpoint.

Controle de acesso baseado em função com autenticação


Kerberos e LDAP no Linux
Consulte a autenticação Kerberos e o RBAC (controle de acesso baseado em função)

Melhorias de API
JSMétodos de extensão ON para HttpRequest e
HttpResponse
JSOs dados ON podem ser lidos e gravados em um HttpRequest e HttpResponse usando
os métodos novos ReadFromJsonAsync e WriteAsJsonAsync de extensão. Esses métodos
de extensão usam o serializador System.Text.Json para lidar com os JSdados ON. O novo
HasJsonContentType método de extensão também pode verificar se uma solicitação tem
um JStipo de conteúdo ON.

Os JSmétodos de extensão ON podem ser combinados com o roteamento de ponto de


extremidade para criar JSAPIs ON em um estilo de programação que chamamos de rota
para código. É uma nova opção para desenvolvedores que desejam criar APIs ON
básicas JSde maneira leve. Por exemplo, um aplicativo Web que tem apenas um
punhado de pontos de extremidade pode optar por usar a rota para codificar em vez da
funcionalidade completa de ASP.NET Core MVC:

C#

endpoints.MapGet("/weather/{city:alpha}", async context =>


{
var city = (string)context.Request.RouteValues["city"];
var weather = GetFromDatabase(city);

await context.Response.WriteAsJsonAsync(weather);
});

System.Diagnostics.Activity
O formato padrão, por System.Diagnostics.Activity enquanto, é padrão para o formato
W3C. Isso torna o suporte de rastreamento distribuído em ASP.NET Core interoperável
com mais estruturas por padrão.

FromBodyAttribute
FromBodyAttribute agora dá suporte à configuração de uma opção que permite que
esses parâmetros ou propriedades sejam considerados opcionais:

C#

public IActionResult Post([FromBody(EmptyBodyBehavior =


EmptyBodyBehavior.Allow)]
MyModel model)
{
...
}

Melhorias diversas
Começamos a aplicar anotações anuláveis a ASP.NET Core assemblies. Planejamos
anotar a maior parte da superfície de API pública comum da estrutura do .NET 5.

Controlar ativação de classe de inicialização


Foi adicionada uma sobrecarga adicional UseStartup que permite que um aplicativo
forneça um método de fábrica para controlar Startup a ativação de classe. Startup
Controlar a ativação de classe é útil para passar parâmetros adicionais para Startup os
quais são inicializados junto com o host:

C#

public class Program


{
public static async Task Main(string[] args)
{
var logger = CreateLogger();
var host = Host.CreateDefaultBuilder()
.ConfigureWebHost(builder =>
{
builder.UseStartup(context => new Startup(logger));
})
.Build();

await host.RunAsync();
}
}

Atualização automática com o relógio dotnet


No .NET 5, a execução do relógio dotnet em um projeto ASP.NET Core inicia o
navegador padrão e atualiza automaticamente o navegador à medida que as alterações
são feitas no código. Isso significa que você pode:

Abra um projeto ASP.NET Core em um editor de texto.


Execute dotnet watch .
Concentre-se nas alterações de código enquanto as ferramentas manipulam a
recompilação, a reinicialização e o recarregamento do aplicativo.
Formator do Agente de Console
Foram feitas melhorias no provedor de log do console na Microsoft.Extensions.Logging
biblioteca. Os desenvolvedores agora podem implementar um controle personalizado
ConsoleFormatter para exercer controle completo sobre a formatação e a coloração da

saída do console. As APIs de formatador permitem uma formatação avançada


implementando um subconjunto das sequências de escape VT-100. O VT-100 tem
suporte na maioria dos terminais modernos. O agente de console pode analisar
sequências de escape em terminais sem suporte, permitindo que os desenvolvedores
criem um único formatador para todos os terminais.

JSAgente do Console ON
Além do suporte para formatores personalizados, também adicionamos um formatador
ON interno JSque emite logs ON estruturados JSno console. O código a seguir mostra
como alternar do agente padrão para JSON:

C#

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddJsonConsole(options =>
{
options.JsonWriterOptions = new JsonWriterOptions()
{ Indented = true };
});
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

As mensagens de log emitidas no console são JSon formatadas:

JSON

{
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Now listening on: https://localhost:5001",
"State": {
"Message": "Now listening on: https://localhost:5001",
"address": "https://localhost:5001",
"{OriginalFormat}": "Now listening on: {address}"
}
}
Novidades no ASP.NET Core 3.1
Artigo • 10/01/2023 • 3 minutos para o fim da leitura

Este artigo destaca as alterações mais significativas no ASP.NET Core 3.1 com links para
a documentação relevante.

Suporte de classe parcial para Razor


componentes
Razor os componentes agora são gerados como classes parciais. O código para um
Razor componente pode ser escrito usando um arquivo code-behind definido como
uma classe parcial em vez de definir todo o código para o componente em um único
arquivo. Para obter mais informações, consulte Suporte de classe parcial.

Auxiliar de Marca de Componente e passa


parâmetros para componentes de nível
superior
Em Blazor com ASP.NET Core 3.0, os componentes foram renderizados em páginas e
exibições usando um Auxiliar html ( Html.RenderComponentAsync ). No ASP.NET Core 3.1,
renderize um componente de uma página ou exibição com o novo Auxiliar de Marca de
Componente:

CSHTML

<component type="typeof(Counter)" render-mode="ServerPrerendered" />

O Auxiliar html permanece com suporte no ASP.NET Core 3.1, mas o Auxiliar de Marca
de Componente é recomendado.

Blazor Server Agora, os aplicativos podem passar parâmetros para componentes de


nível superior durante a renderização inicial. Anteriormente, você só podia passar
parâmetros para um componente de nível superior com RenderMode.Static. Com esta
versão, há suporte para RenderMode.Server e RenderMode.ServerPrerendered . Todos
os valores de parâmetro especificados são serializados como JSON e incluídos na
resposta inicial.
Por exemplo, pré-gerar um Counter componente com uma quantidade de incremento
( IncrementAmount ):

CSHTML

<component type="typeof(Counter)" render-mode="ServerPrerendered"


param-IncrementAmount="10" />

Para obter mais informações, consulte Integrar componentes em Razor aplicativos Pages
e MVC.

Suporte para filas compartilhadas no HTTP.sys


HTTP.sysdá suporte à criação de filas de solicitação anônima. No ASP.NET Core 3.1,
adicionamos a capacidade de criar ou anexar a uma fila de solicitação de HTTP.sys
nomeada existente. Criar ou anexar a uma fila de solicitação de HTTP.sys nomeada
existente permite cenários em que o processo do controlador de HTTP.sys que possui a
fila é independente do processo do ouvinte. Essa independência possibilita preservar
conexões existentes e solicitações enfileiradas entre reinicializações do processo do
ouvinte:

C#

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// ...
webBuilder.UseHttpSys(options =>
{
options.RequestQueueName = "MyExistingQueue";
options.RequestQueueMode = RequestQueueMode.CreateOrAttach;
});
});

Alterações interruptivas para SameSite cookies


O comportamento do SameSite cookies foi alterado para refletir as próximas alterações
no navegador. Isso pode afetar cenários de autenticação como AzureAd,
OpenIdConnect ou WsFederation. Para obter mais informações, consulte Trabalhar com
SameSite cookies no ASP.NET Core.
Impedir ações padrão para eventos em Blazor
aplicativos
Use o @on{EVENT}:preventDefault atributo de diretiva para impedir a ação padrão de
um evento. No exemplo a seguir, a ação padrão de exibir o caractere da chave na caixa
de texto é impedida:

razor

<input value="@_count" @onkeypress="KeyHandler" @onkeypress:preventDefault


/>

Para obter mais informações, consulte Impedir ações padrão.

Interromper a propagação de eventos em


Blazor aplicativos
Use o @on{EVENT}:stopPropagation atributo de diretiva para interromper a propagação
de eventos. No exemplo a seguir, marcar a caixa de seleção impede que eventos de
clique do filho <div> se propaguem para o pai <div> :

razor

<input @bind="_stopPropagation" type="checkbox" />

<div @onclick="OnSelectParentDiv">
<div @onclick="OnSelectChildDiv"
@onclick:stopPropagation="_stopPropagation">
...
</div>
</div>

@code {
private bool _stopPropagation = false;
}

Para obter mais informações, consulte Parar propagação de eventos.

Erros detalhados durante Blazor o


desenvolvimento de aplicativos
Quando um Blazor aplicativo não está funcionando corretamente durante o
desenvolvimento, receber informações detalhadas de erro do aplicativo ajuda na
solução de problemas e na correção do problema. Quando ocorre um erro, Blazor os
aplicativos exibem uma barra de ouro na parte inferior da tela:

Durante o desenvolvimento, a barra de ouro direciona você para o console do


navegador, onde você pode ver a exceção.
Na produção, a barra de ouro notifica o usuário de que ocorreu um erro e
recomenda a atualização do navegador.

Para obter mais informações, consulte Tratar erros em aplicativos ASP.NET CoreBlazor.
Novidades no ASP.NET Core 3.0
Artigo • 10/01/2023 • 16 minutos para o fim da leitura

Este artigo destaca as alterações mais significativas no ASP.NET Core 3.0 com links para
a documentação relevante.

Blazor
Blazoré uma nova estrutura no ASP.NET Core para criar uma interface do usuário da
Web interativa do lado do cliente com o .NET:

Crie interfaces do usuário interativas avançadas usando C# em vez de JavaScript.


Compartilhe a lógica de aplicativo do lado do cliente e do servidor gravada no
.NET.
Renderize a interface do usuário, como HTML e CSS para suporte amplo de
navegadores, incluindo navegadores móveis.

Blazor cenários com suporte da estrutura:

Componentes de interface do usuário reutilizáveis (Razor componentes)


Roteamento do lado do cliente
Layouts de componente
Suporte para injeção de dependência
Formulários e validação
Fornecer Razor componentes em Razor bibliotecas de classes
Interoperabilidade do JavaScript

Para obter mais informações, consulte ASP.NET Core Blazor.

Blazor Server
Blazor separa a lógica de renderização de componentes de como as atualizações de
interface do usuário são aplicadas. O Blazor Server dá suporte para hospedar os
componentes do Razor no servidor em um aplicativo ASP.NET Core. As atualizações da
interface do usuário são tratadas por uma conexão SignalR. Blazor Serverhá suporte no
ASP.NET Core 3.0.

Blazor WebAssembly (versão prévia)


Blazor os aplicativos também podem ser executados diretamente no navegador usando
um runtime do .NET baseado em WebAssembly. Blazor WebAssemblyestá em versão
prévia e não tem suporte no ASP.NET Core 3.0. Blazor WebAssemblyhaverá suporte em
uma versão futura do ASP.NET Core.

Componentes Razor
Blazor os aplicativos são criados a partir de componentes. Os componentes são partes
autossuficientes da interface do usuário (interface do usuário), como uma página, caixa
de diálogo ou formulário. Os componentes são classes .NET normais que definem a
lógica de renderização da interface do usuário e os manipuladores de eventos do lado
do cliente. Você pode criar aplicativos Web interativos avançados sem JavaScript.

Os componentes em Blazor normalmente são criados usando Razor sintaxe, uma


mistura natural de HTML e C#. Razor os componentes são semelhantes aos Razor
modos de exibição Pages e MVC, pois ambos usam Razor. Ao contrário das páginas e
exibições, que se baseiam em um modelo de solicitação-resposta, os componentes são
usados especificamente para lidar com a composição da interface do usuário.

gRPC
gRPC :

É uma estrutura RPC popular e de alto desempenho (chamada de procedimento


remoto).

Oferece uma abordagem de primeiro contrato opinativa para o desenvolvimento


de API.

Usa tecnologias modernas como:


HTTP/2 para transporte.
Buffers de protocolo como a linguagem de descrição da interface.
Formato de serialização binária.

Fornece recursos como:


Autenticação
Streaming bidirecional e controle de fluxo.
Cancelamento e tempos limite.

A funcionalidade gRPC no ASP.NET Core 3.0 inclui:

Grpc.AspNetCore : uma estrutura ASP.NET Core para hospedar serviços gRPC. O


gRPC no ASP.NET Core integra-se aos recursos de ASP.NET Core padrão, como
registro em log, DI (injeção de dependência), autenticação e autorização.
Grpc.Net.Client : um cliente gRPC para .NET Core que se baseia no familiar
HttpClient .
Grpc.Net.ClientFactory : integração do cliente gRPC com HttpClientFactory .

Para obter mais informações, consulte Visão geral do gRPC no .NET.

SignalR
Consulte Atualizar SignalR código para obter instruções de migração. SignalR agora usa
System.Text.Json para serializar/desserializar JSmensagens ON. Consulte Alternar para

Newtonsoft.Json para obter instruções para restaurar o Newtonsoft.Json serializador


baseado em .

Nos clientes JavaScript e .NET para SignalR, o suporte foi adicionado para reconexão
automática. Por padrão, o cliente tenta se reconectar imediatamente e tentar
novamente após 2, 10 e 30 segundos, se necessário. Se o cliente se reconectar com
êxito, ele receberá uma nova ID de conexão. A reconexão automática é aceita:

JavaScript

const connection = new signalR.HubConnectionBuilder()


.withUrl("/chathub")
.withAutomaticReconnect()
.build();

Os intervalos de reconexão podem ser especificados passando uma matriz de durações


baseadas em milissegundos:

JavaScript

.withAutomaticReconnect([0, 3000, 5000, 10000, 15000, 30000])


//.withAutomaticReconnect([0, 2000, 10000, 30000]) The default intervals.

Uma implementação personalizada pode ser passada para controle total dos intervalos
de reconexão.

Se a reconexão falhar após o último intervalo de reconexão:

O cliente considera que a conexão está offline.


O cliente para de tentar se reconectar.

Durante as tentativas de reconexão, atualize a interface do usuário do aplicativo para


notificar o usuário de que a reconexão está sendo tentada.
Para fornecer comentários da interface do usuário quando a conexão for interrompida, a
API do SignalR cliente foi expandida para incluir os seguintes manipuladores de eventos:

onreconnecting : dá aos desenvolvedores a oportunidade de desabilitar a interface

do usuário ou informar aos usuários que o aplicativo está offline.


onreconnected : dá aos desenvolvedores a oportunidade de atualizar a interface do

usuário depois que a conexão for restabelecida.

O código a seguir usa onreconnecting para atualizar a interface do usuário ao tentar se


conectar:

JavaScript

connection.onreconnecting((error) => {
const status = `Connection lost due to error "${error}". Reconnecting.`;
document.getElementById("messageInput").disabled = true;
document.getElementById("sendButton").disabled = true;
document.getElementById("connectionStatus").innerText = status;
});

O código a seguir usa onreconnected para atualizar a interface do usuário na conexão:

JavaScript

connection.onreconnected((connectionId) => {
const status = `Connection reestablished. Connected.`;
document.getElementById("messageInput").disabled = false;
document.getElementById("sendButton").disabled = false;
document.getElementById("connectionStatus").innerText = status;
});

SignalR 3.0 e posterior fornece um recurso personalizado para manipuladores de


autorização quando um método de hub requer autorização. O recurso é uma instância
do HubInvocationContext . O HubInvocationContext inclui:

HubCallerContext

Nome do método hub que está sendo invocado.


Argumentos para o método hub.

Considere o exemplo a seguir de um aplicativo de sala de chat que permite a entrada de


várias organizações por meio do Azure Active Directory. Qualquer pessoa com uma
conta microsoft pode entrar no chat, mas apenas membros da organização proprietária
podem proibir usuários ou exibir históricos de chat dos usuários. O aplicativo pode
restringir determinadas funcionalidades de usuários específicos.
C#

public class DomainRestrictedRequirement :


AuthorizationHandler<DomainRestrictedRequirement, HubInvocationContext>,
IAuthorizationRequirement
{
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
DomainRestrictedRequirement requirement,
HubInvocationContext resource)
{
if (context.User?.Identity?.Name == null)
{
return Task.CompletedTask;
}

if (IsUserAllowedToDoThis(resource.HubMethodName,
context.User.Identity.Name))
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}

private bool IsUserAllowedToDoThis(string hubMethodName, string


currentUsername)
{
if (hubMethodName.Equals("banUser",
StringComparison.OrdinalIgnoreCase))
{
return currentUsername.Equals("bob42@jabbr.net",
StringComparison.OrdinalIgnoreCase);
}

return currentUsername.EndsWith("@jabbr.net",
StringComparison.OrdinalIgnoreCase));
}
}

No código anterior, DomainRestrictedRequirement serve como um personalizado


IAuthorizationRequirement . Como o HubInvocationContext parâmetro de recurso está
sendo passado, a lógica interna pode:

Inspecione o contexto no qual o Hub está sendo chamado.


Tome decisões sobre como permitir que o usuário execute métodos de Hub
individuais.

Métodos de Hub Individuais podem ser marcados com o nome da política que o código
verifica em tempo de execução. À medida que os clientes tentam chamar métodos de
Hub individuais, o DomainRestrictedRequirement manipulador executa e controla o
acesso aos métodos. Com base na maneira como os DomainRestrictedRequirement
controles acessam:

Todos os usuários conectados podem chamar o SendMessage método .


Somente os usuários que fizeram logon com um @jabbr.net endereço de email
podem exibir os históricos dos usuários.
Somente bob42@jabbr.net os usuários podem proibir usuários da sala de chat.

C#

[Authorize]
public class ChatHub : Hub
{
public void SendMessage(string message)
{
}

[Authorize("DomainRestricted")]
public void BanUser(string username)
{
}

[Authorize("DomainRestricted")]
public void ViewUserHistory(string username)
{
}
}

A criação da DomainRestricted política pode envolver:

No Startup.cs , adicionando a nova política.


Forneça o requisito personalizado DomainRestrictedRequirement como um
parâmetro.
Registrando-se DomainRestricted com o middleware de autorização.

C#

services
.AddAuthorization(options =>
{
options.AddPolicy("DomainRestricted", policy =>
{
policy.Requirements.Add(new DomainRestrictedRequirement());
});
});
SignalR os hubs usam o Roteamento de Ponto de Extremidade. SignalR A conexão do
hub foi feita explicitamente:

C#

app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("hubs/chat");
});

Na versão anterior, os desenvolvedores precisavam conectar controladores, Razor


páginas e hubs em uma variedade de locais. A conexão explícita resulta em uma série
de segmentos de roteamento quase idênticos:

C#

app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("hubs/chat");
});

app.UseRouting(routes =>
{
routes.MapRazorPages();
});

SignalR Os hubs 3.0 podem ser roteados por meio do roteamento de ponto de
extremidade. Com o roteamento de ponto de extremidade, normalmente todo o
roteamento pode ser configurado em UseRouting :

C#

app.UseRouting(routes =>
{
routes.MapRazorPages();
routes.MapHub<ChatHub>("hubs/chat");
});

ASP.NET Core 3.0 SignalR adicionado:

Streaming de cliente para servidor. Com o streaming de cliente para servidor, os


métodos do lado do servidor podem usar instâncias de um IAsyncEnumerable<T> ou
ChannelReader<T> . No seguinte exemplo de C#, o UploadStream método no Hub

receberá um fluxo de cadeias de caracteres do cliente:

C#
public async Task UploadStream(IAsyncEnumerable<string> stream)
{
await foreach (var item in stream)
{
// process content
}
}

Os aplicativos cliente .NET podem passar uma IAsyncEnumerable<T> instância ou


ChannelReader<T> como o stream argumento do UploadStream método Hub acima.

Depois que o for loop for concluído e a função local for encerrada, a conclusão do
fluxo será enviada:

C#

async IAsyncEnumerable<string> clientStreamData()


{
for (var i = 0; i < 5; i++)
{
var data = await FetchSomeData();
yield return data;
}
}

await connection.SendAsync("UploadStream", clientStreamData());

Os aplicativos cliente JavaScript usam o SignalR Subject (ou um Assunto RxJS ) para o
stream argumento do UploadStream método Hub acima.

JavaScript

let subject = new signalR.Subject();


await connection.send("StartStream", "MyAsciiArtStream", subject);

O código JavaScript pode usar o subject.next método para manipular cadeias de


caracteres conforme elas são capturadas e prontas para serem enviadas ao servidor.

JavaScript

subject.next("example");
subject.complete();

Usando código como os dois snippets anteriores, experiências de streaming em tempo


real podem ser criadas.
Nova JSserialização ON
ASP.NET Core 3.0 agora usa System.Text.Json por padrão para JSserialização ON:

Lê e grava JSON de forma assíncrona.


É otimizado para texto UTF-8.
Normalmente, um desempenho mais alto do que Newtonsoft.Json .

Para adicionar Json.NET ao ASP.NET Core 3.0, consulte Adicionar suporte ao formato ON
baseado em JSNewtonsoft.Json.

Novas Razor diretivas


A lista a seguir contém novas Razor diretivas:

@attribute: a @attribute diretiva aplica o atributo determinado à classe da página


ou exibição gerada. Por exemplo, @attribute [Authorize] .
@implements: a @implements diretiva implementa uma interface para a classe
gerada. Por exemplo, @implements IDisposable .

IdentityO Server4 dá suporte à autenticação e


autorização para APIs Web e SPAs
ASP.NET Core 3.0 oferece autenticação em SPAs (Aplicativos de Página Única) usando o
suporte para autorização de API Web. Identity ASP.NET Core para autenticar e
armazenar usuários é combinado com Identityo Server4 para implementar o OpenID
Connect.

IdentityServer4 é uma estrutura OpenID Connect e OAuth 2.0 para ASP.NET Core 3.0. Ele
habilita os seguintes recursos de segurança:

Autenticação como serviço (AaaS)


SSO (logon único) em vários tipos de aplicativo
Controle de acesso para APIs
Gateway de Federação

Para obter mais informações, consulte a documentação do IdentityServer4 ou


Autenticação e autorização para SPAs.

Autenticação De certificado e Kerberos


A autenticação de certificado requer:

Configurando o servidor para aceitar certificados.


Adicionando o middleware de autenticação no Startup.Configure .
Adicionando o serviço de autenticação de certificado no
Startup.ConfigureServices .

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
// Other service configuration removed.
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
app.UseAuthentication();
// Other app configuration removed.
}

As opções de autenticação de certificado incluem a capacidade de:

Aceite certificados autoassinados.


Verifique se há revogação de certificado.
Verifique se o certificado oferecido tem os sinalizadores de uso corretos.

Uma entidade de usuário padrão é construída com base nas propriedades do


certificado. A entidade de segurança do usuário contém um evento que permite
complementar ou substituir a entidade de segurança. Para obter mais informações,
consulte Configurar a autenticação de certificado no ASP.NET Core.

A Autenticação do Windows foi estendida para Linux e macOS. Nas versões anteriores, a
Autenticação do Windows era limitada ao IIS e HTTP.sys. No ASP.NET Core 3.0, Kestrel
tem a capacidade de usar Negotiate, Kerberos e NTLM no Windows, Linux e macOS
para hosts ingressados no domínio do Windows. Kestrel O suporte a esses esquemas de
autenticação é fornecido pelo pacote NuGet
Microsoft.AspNetCore.Authentication.Negotiate . Assim como acontece com os
outros serviços de autenticação, configure o aplicativo de autenticação em todo o e
configure o serviço:

C#
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
// Other service configuration removed.
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
app.UseAuthentication();
// Other app configuration removed.
}

Requisitos de host:

Os hosts do Windows devem ter SPNs ( Nomes de Entidade de Serviço )


adicionados à conta de usuário que hospeda o aplicativo.
Os computadores Linux e macOS devem ser ingressados no domínio.
Os SPNs devem ser criados para o processo da Web.
Os arquivos keytab devem ser gerados e configurados no computador host.

Para obter mais informações, consulte Configurar a Autenticação do Windows no


ASP.NET Core.

Alterações de modelo
Os modelos de interface do usuário da Web (Razor Páginas, MVC com controlador e
exibições) foram removidos:

A cookie interface do usuário de consentimento não está mais incluída. Para


habilitar o cookie recurso de consentimento em um aplicativo gerado por modelo
ASP.NET Core 3.0, confira Suporte ao RGPD (Regulamento Geral sobre a Proteção
de Dados) em ASP.NET Core.
Scripts e ativos estáticos relacionados agora são referenciados como arquivos
locais em vez de usar CDNs. Para obter mais informações, consulte Scripts e ativos
estáticos relacionados agora são referenciados como arquivos locais em vez de
usar CDNs com base no ambiente atual (dotnet/AspNetCore.Docs #14350) .

O modelo Angular atualizado para usar Angular 8.

O Razor modelo de RCL (biblioteca de classes) usa como padrão o Razor


desenvolvimento de componentes. Uma nova opção de modelo no Visual Studio
fornece suporte de modelo para páginas e exibições. Ao criar uma RCL a partir do
modelo em um shell de comando, passe a opção --support-pages-and-views ( dotnet
new razorclasslib --support-pages-and-views ).

Host Genérico
Os modelos do ASP.NET Core 3.0 usam o Host Genérico do .NET em ASP.NET Core.
Versões anteriores usavam WebHostBuilder. O uso do Host Genérico do .NET Core
(HostBuilder) fornece uma melhor integração de aplicativos ASP.NET Core com outros
cenários de servidor que não são específicos da Web. Para obter mais informações,
consulte HostBuilder substitui WebHostBuilder.

Configuração do host
Antes do lançamento do ASP.NET Core 3.0, variáveis de ambiente prefixadas com
ASPNETCORE_ eram carregadas para a configuração de host do Host da Web. Na 3.0,

AddEnvironmentVariables é usado para carregar variáveis de ambiente prefixadas com


DOTNET_ para a configuração do host com CreateDefaultBuilder .

Alterações na injeção de construtor de inicialização


O Host Genérico dá suporte apenas aos seguintes tipos para Startup injeção de
construtor:

IHostEnvironment
IWebHostEnvironment
IConfiguration

Todos os serviços ainda podem ser injetados diretamente como argumentos para o
Startup.Configure método . Para obter mais informações, consulte Host Genérico

restringe a injeção de construtor de inicialização (aspnet/Announcements #353) .

Kestrel
Kestrel A configuração foi atualizada para a migração para o Host Genérico. Na
versão 3.0, Kestrel é configurado no construtor de host da Web fornecido pelo
ConfigureWebHostDefaults .

Os adaptadores de Kestrel conexão foram removidos e substituídos pelo


Middleware de Conexão, que é semelhante ao Middleware HTTP no pipeline de
ASP.NET Core, mas para conexões de nível inferior.
A Kestrel camada de transporte foi exposta como uma interface pública no
Connections.Abstractions .
A ambiguidade entre cabeçalhos e trailers foi resolvida movendo cabeçalhos à
direita para uma nova coleção.
APIs de E/S síncronas, como HttpRequest.Body.Read , são uma fonte comum de
fome de thread que leva a falhas de aplicativo. Na versão 3.0, AllowSynchronousIO
é desabilitado por padrão.

Para obter mais informações, consulte Migrar de ASP.NET Core 2.2 para 3.0.

HTTP/2 habilitado por padrão


O HTTP/2 está habilitado por padrão no Kestrel para pontos de extremidade HTTPS. O
suporte a HTTP/2 para IIS ou HTTP.sys é habilitado quando compatível com o sistema
operacional.

EventCounters na solicitação
O EventSource de Hospedagem, Microsoft.AspNetCore.Hosting , emite os seguintes
novos EventCounter tipos relacionados a solicitações de entrada:

requests-per-second
total-requests

current-requests
failed-requests

Roteamento de ponto de extremidade


O Roteamento de Ponto de Extremidade, que permite que estruturas (por exemplo,
MVC) funcionem bem com middleware, é aprimorado:

A ordem do middleware e dos pontos de extremidade é configurável no pipeline


de processamento de solicitação do Startup.Configure .
Os pontos de extremidade e o middleware compõem-se bem com outras
tecnologias baseadas em ASP.NET Core, como verificações de integridade.
Os pontos de extremidade podem implementar uma política, como CORS ou
autorização, no middleware e no MVC.
Filtros e atributos podem ser colocados em métodos em controladores.

Saiba mais em Roteamento no ASP.NET Core.


Verificações de Integridade
As Verificações de Integridade usam o roteamento de ponto de extremidade com o
Host Genérico. Em Startup.Configure , chame MapHealthChecks no construtor de ponto
de extremidade com a URL do ponto de extremidade ou o caminho relativo:

C#

app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health");
});

Os pontos de extremidade de Verificações de Integridade podem:

Especifique um ou mais hosts/portas permitidos.


Exigir autorização.
Exigir CORS.

Para obter mais informações, consulte os seguintes artigos:

Migrar do ASP.NET Core 2.2 para o 3.0


Verificações de integridade no ASP.NET Core

Pipes em HttpContext
Agora é possível ler o corpo da solicitação e gravar o corpo da resposta usando a
System.IO.Pipelines API. A HttpRequest.BodyReader propriedade fornece um PipeReader
que pode ser usado para ler o corpo da solicitação. A HttpResponse.BodyWriter
propriedade fornece um PipeWriter que pode ser usado para gravar o corpo da
resposta. HttpRequest.BodyReader é um análogo do HttpRequest.Body fluxo.
HttpResponse.BodyWriter é um análogo do HttpResponse.Body fluxo.

Relatórios de erros aprimorados no IIS


Os erros de inicialização ao hospedar aplicativos ASP.NET Core no IIS agora produzem
dados de diagnóstico mais avançados. Esses erros são relatados ao Log de Eventos do
Windows com rastreamentos de pilha sempre que aplicável. Além disso, todos os avisos,
erros e exceções sem tratamento são registrados no Log de Eventos do Windows.

SDK de Trabalho e Serviço de Trabalho


O .NET Core 3.0 apresenta o novo modelo de aplicativo do Serviço de Trabalho. Este
modelo fornece um ponto de partida para escrever serviços de longa execução no .NET
Core.

Para obter mais informações, consulte:

Trabalhos do .NET Core como Serviços windows


Tarefas em segundo plano com serviços hospedados no ASP.NET Core
Hospedar o ASP.NET Core em um serviço Windows

Melhorias de middleware de cabeçalhos


encaminhados
Nas versões anteriores do ASP.NET Core, chamar UseHsts e UseHttpsRedirection era
problemático quando implantado em um Linux do Azure ou atrás de qualquer proxy
reverso diferente do IIS. A correção para versões anteriores está documentada em
Encaminhar o esquema para proxies reversos linux e não IIS.

Esse cenário é corrigido no ASP.NET Core 3.0. O host habilita o Middleware cabeçalhos
encaminhados quando a ASPNETCORE_FORWARDEDHEADERS_ENABLED variável de ambiente é
definida como true . ASPNETCORE_FORWARDEDHEADERS_ENABLED é definido como true em
nossas imagens de contêiner.

Aprimoramentos de desempenho
ASP.NET Core 3.0 inclui muitos aprimoramentos que reduzem o uso de memória e
melhoram a taxa de transferência:

Redução no uso de memória ao usar o contêiner de injeção de dependência


interno para serviços com escopo.
Redução de alocações em toda a estrutura, incluindo cenários de middleware e
roteamento.
Redução no uso de memória para conexões WebSocket.
Redução de memória e melhorias de taxa de transferência para conexões HTTPS.
Novo serializador ON otimizado e totalmente assíncrono JS.
Redução no uso de memória e melhorias de taxa de transferência na análise de
formulários.

ASP.NET Core 3.0 só é executado no .NET Core


3.0
A partir do ASP.NET Core 3.0, .NET Framework não é mais uma estrutura de destino com
suporte. Projetos direcionados .NET Framework podem continuar de forma totalmente
compatível usando a versão do .NET Core 2.1 LTS . A maioria dos pacotes relacionados
ASP.NET Core 2.1.x terá suporte indefinidamente, além do período lts de três anos para
o .NET Core 2.1.

Para obter informações de migração, consulte Portar seu código de .NET Framework
para o .NET Core.

Usar a estrutura compartilhada ASP.NET Core


A estrutura compartilhada ASP.NET Core 3.0, contida no metapacote
Microsoft.AspNetCore.App, não requer mais um elemento explícito <PackageReference
/> no arquivo de projeto. A estrutura compartilhada é referenciada automaticamente ao
usar o Microsoft.NET.Sdk.Web SDK no arquivo de projeto:

XML

<Project Sdk="Microsoft.NET.Sdk.Web">

Assemblies removidos da estrutura


compartilhada ASP.NET Core
Os assemblies mais notáveis removidos da estrutura compartilhada ASP.NET Core 3.0
são:

Newtonsoft.Json (Json.NET). Para adicionar Json.NET ao ASP.NET Core 3.0,


consulte Adicionar suporte ao formato ON baseado em JSNewtonsoft.Json.
ASP.NET Core 3.0 apresenta System.Text.Json para leitura e gravação JSon. Para
obter mais informações, consulte Nova JSserialização ON neste documento.
Entity Framework Core

Para obter uma lista completa de assemblies removidos da estrutura compartilhada,


consulte Assemblies sendo removidos do Microsoft.AspNetCore.App 3.0 . Para obter
mais informações sobre a motivação dessa alteração, consulte Alterações interruptivas
em Microsoft.AspNetCore.App na versão 3.0 e Uma primeira olhada nas alterações
que estão chegando no ASP.NET Core 3.0 .
Novidades do ASP.NET Core 2.2
Artigo • 10/01/2023 • 6 minutos para o fim da leitura

Este artigo destaca as alterações mais significativas no ASP.NET Core 2.2, com links para
a documentação relevante.

Convenções de analisadores & de OpenAPI


OpenAPI (anteriormente conhecido como Swagger) é uma especificação independente
de linguagem para descrever REST APIs. O ecossistema do Open API tem ferramentas
que permitem descobrir, testar e produzir o código do cliente usando a especificação. O
suporte para gerar e visualizar documentos OpenAPI no ASP.NET Core MVC é fornecido
por meio de projetos orientados pela comunidade, como NSwag e
Swashbuckle.AspNetCore . O ASP.NET Core 2.2 fornece ferramentas e experiências de
runtime aprimoradas para a criação de documentos do OpenAPI.

Para saber mais, consulte os recursos a seguir:

Usar os analisadores da API Web


Usar convenções de API Web
ASP.NET Core 2.2.0-preview1: Analisadores & de OpenAPI Convenções

Suporte de detalhes do problema


O ASP.NET Core 2.1 introduziu o ProblemDetails , com base na especificação RFC
7807 para transmitir os detalhes de um erro com uma Resposta HTTP. No 2.2,
ProblemDetails é a resposta padrão para códigos de erro do cliente em controladores
atribuídos com ApiControllerAttribute . Um IActionResult que retorna um código de
status de erro do cliente (4xx) retorna um corpo ProblemDetails . O resultado também
inclui uma ID de correlação que pode ser usada para correlacionar o erro usando logs
de solicitação. Para erros do cliente, ProducesResponseType usa como padrão
ProblemDetails como o tipo de resposta. Isso é documentado na saída do
OpenAPI/Swagger gerada com o NSwag ou o Swashbuckle.AspNetCore.

Roteamento de ponto de extremidade


O ASP.NET Core 2.2 usa um novo sistema de roteamento de ponto de extremidade para
expedição aprimorada de solicitações. As alterações incluem novos membros da API de
geração de link e transformadores de parâmetro de rota.
Para saber mais, consulte os recursos a seguir:

Roteamento de ponto de extremidade no 2.2


Transformadores de parâmetro de rota (confira a seção Roteamento)
Diferenças entre o roteamento baseado em IRouter e em ponto de extremidade

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 em Kestrel
O ASP.NET Core 2.2 adiciona suporte ao HTTP/2.

O HTTP/2 é uma revisão principal do protocolo HTTP. Os recursos notáveis do HTTP/2


incluem:

Suporte para compactação de cabeçalho.


Fluxos totalmente multiplexados em uma única conexão.

Embora HTTP/2 preserve a semântica do HTTP (por exemplo, cabeçalhos e métodos


HTTP), é uma alteração interruptiva de HTTP/1.x sobre como os dados são enquadrados
e enviados entre o cliente e o servidor.

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 de Kestrel
Em versões anteriores do ASP.NET Core, Kestrel as opções são configuradas chamando
UseKestrel . Na versão 2.2, Kestrel as opções são configuradas chamando

ConfigureKestrel no construtor de host. Essa alteração resolve um problema com a

ordem dos registros IServer para a hospedagem em processo. Para saber mais,
consulte os recursos a seguir:

Atenuar conflitos do UseIIS


Configurar Kestrel opções de servidor com ConfigurarKestrel

Hospedagem em processo do IIS


Em versões anteriores do ASP.NET Core, o IIS funciona como um proxy reverso. No 2.2,
o Módulo do ASP.NET Core pode inicializar o CoreCLR e hospedar um aplicativo dentro
do processo de trabalho do IIS (w3wp.exe). A hospedagem em processo fornece ganhos
de desempenho e diagnóstico durante a execução com o IIS.

Para obter mais informações, confira Hospedagem em processo para IIS.

SignalR Cliente Java


ASP.NET Core 2.2 apresenta um Cliente Java para SignalR. Esse cliente dá suporte à
conexão com um servidor ASP.NET Core SignalR do código Java, incluindo aplicativos
Android.

Para obter mais informações, consulte ASP.NET Core SignalR cliente Java.
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 .

Para obter mais informações, confira Middleware do CORS.

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 eo
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 por solicitação quais validadores se aplicam a um determinado
modelo. Isso é ótimo para a criação de provedores de validação complexa. No entanto,
no caso mais comum, 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.

Desempenho do Cliente HTTP


No ASP.NET Core 2.2, o desempenho do SocketsHttpHandler foi aprimorado, reduzindo
a contenção de bloqueio do pool de conexão. Para aplicativos que fazem muitas
solicitações HTTP de saída, como algumas arquiteturas de microsserviços, a taxa de
transferência foi aprimorada. Sob carga, a taxa de transferência do HttpClient pode ser
melhorada em até 60% no Linux e 20% no Windows.

Para obter mais informações, confira a solicitação de pull que fez essa melhoria .

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
Artigo • 10/01/2023 • 6 minutos para o fim da leitura

Este artigo destaca as alterações mais significativas no ASP.NET Core 2.1, com links para
a documentação relevante.

SignalR
SignalRfoi reescrito para ASP.NET Core 2.1.

SignalR 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, consulte ASP.NET Core SignalR.

Razor bibliotecas de classes


ASP.NET Core 2.1 facilita a compilação e a inclusão Razorda interface do usuário
baseada em uma biblioteca e compartilhá-la em vários projetos. O novo Razor SDK
permite a criação de Razor arquivos em um projeto de biblioteca de classes que pode
ser empacotado em um pacote NuGet. Exibições e páginas em bibliotecas são
descobertas automaticamente e podem ser substituídas pelo aplicativo. Integrando a
Razor compilação ao build:

O tempo de inicialização do aplicativo é significativamente mais rápido.


Atualizações rápidas para Razor exibições e páginas em runtime ainda estão
disponíveis como parte de um fluxo de trabalho de desenvolvimento iterativo.

Para obter mais informações, consulte Criar interface do usuário reutilizável usando o
Razor projeto biblioteca de classes.

Identity Scaffolding da biblioteca & de


interface do usuário
ASP.NET Core 2.1 fornece ASP.NET Core Identity como uma Razor biblioteca de classes.
Os aplicativos que incluem Identity podem aplicar o novo Identity scaffolder para
adicionar seletivamente o código-fonte contido na IdentityRazor RCL (Biblioteca de
Classes). Talvez você queira gerar o código-fonte para que você possa modificar o
código e alterar o comportamento. Por exemplo, você pode instruir o scaffolder a gerar
o código usado no registro. O código gerado tem precedência sobre o mesmo código
na Identity RCL.

Aplicativos que não incluem autenticação podem aplicar a Identity scaffolder para
adicionar o pacote RCL Identity . Você tem a opção de selecionar Identity o código a ser
gerado.

Para obter mais informações, consulte Scaffold Identity em projetos ASP.NET Core.

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 mais informações, consulte Impor o HTTPS.

Ativado por padrão


Para facilitar o desenvolvimento seguro de sites, o HTTPS agora está habilitado por
padrão. A partir da 2.1, Kestrel escuta 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, o esquema
de configuração padrão para a configuração do HTTPS foi Kestrel adicionado. Os
aplicativos podem ser configurados para usar:

Vários pontos de extremidade incluindo as URLs. Para obter mais informações,


consulte Kestrel Implementação do servidor Web: configuração do ponto de
extremidade.
O certificado a ser usado para HTTPS de um arquivo no disco ou de um repositório
de certificados.

GDPR
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 WebApplicationFactory<TEntryPoint> classe 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:

C#

public class BasicTests


: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly HttpClient _client;

public BasicTests(WebApplicationFactory<RazorPagesProject.Startup>
factory)
{
_client = factory.CreateClient();
}

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

Para obter mais informações, veja o tópico Testes de integração.

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

Kestrel Configuração de transporte libuv


Com o lançamento do ASP.NET Core 2.1, Kestrelo transporte padrão não é mais
baseado no Libuv, mas sim com base em soquetes gerenciados. Para obter mais
informações, consulte Kestrel Implementação do servidor Web: configuração de
transporte libuv.

Construtor de host genérico


O Construtor de Host Genérico ( HostBuilder ) foi introduzido. Este construtor pode ser
usado para aplicativos que não processam solicitações HTTP (mensagens, tarefas em
segundo plano etc.).

Para saber mais, confira Host Genérico do .NET.

Modelos do SPA atualizados


Os modelos de Aplicativo de Página Única para Angular, React e React com Redux são
atualizados para usar estruturas de projeto padrão e criar sistemas para cada estrutura.

O modelo Angular se baseia na CLI Angular e os modelos React baseiam-se em create-


react-app.

Para obter mais informações, consulte:

Usar Angular com ASP.NET Core


Usar React com ASP.NET Core
Usar o modelo de projeto do React com Redux com o ASP.NET Core

Razor Páginas pesquisam Razor ativos


Na versão 2.1, Razor o Pages pesquisa Razor ativos (como layouts e parciais) nos
seguintes diretórios na ordem listada:

1. Pasta de Páginas atual.


2. /Pages/Shared/
3. /Views/Shared/

Razor Páginas em uma área


Razor As páginas agora dão suporte a áreas. Para ver um exemplo de áreas, crie um
novo Razor aplicativo Web Pages com contas de usuário individuais. Um Razor
aplicativo Web Pages com contas de usuário individuais inclui /Areas/Identity/Pages.

Versão de compatibilidade do MVC


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 para ASP.NET Core
MVC.

Migrar de 2.0 para 2.1


Veja Migrar do ASP.NET Core 2.0 para 2.1.

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
Artigo • 10/01/2023 • 6 minutos para o fim da leitura

Este artigo destaca as alterações mais significativas no ASP.NET Core 2.0, com links para
a documentação relevante.

Razor Pages
RazorPages é um novo recurso de ASP.NET Core MVC que torna os cenários focados em
página de codificação mais fáceis e produtivos.

Para obter mais informações, consulte a introdução e o tutorial:

Introdução às Razor Páginas


Introdução ao Razor Pages

Metapacote do ASP.NET Core


Um novo metapacote do ASP.NET Core inclui todos os pacotes feitos e com suporte
pelas equipes do ASP.NET Core e do Entity Framework Core, juntamente com as
respectivas dependências internas e de terceiros. Você não precisa mais escolher
recursos individuais do ASP.NET Core por pacote. Todos os recursos estão incluídos no
pacote Microsoft.AspNetCore.All . Os modelos padrão usam este pacote.

Para obter mais informações, consulte Metapacote do Microsoft.AspNetCore.All para


ASP.NET Core 2.0.

Repositório de runtime
Aplicativos que usam o metapacote Microsoft.AspNetCore.All aproveitam
automaticamente o novo repositório de runtime do .NET Core. O repositório contém
todos os ativos de runtime necessários para executar aplicativos ASP.NET Core 2.0.
Quando você usa o metapacote Microsoft.AspNetCore.All , nenhum ativo dos pacotes
NuGet do ASP.NET Core referenciados são implantados com o aplicativo porque eles já
estão no sistema de destino. Os ativos no repositório de runtime também são pré-
compilados para melhorar o tempo de inicialização do aplicativo.

Para obter mais informações, consulte Repositório de runtime


.NET Standard 2.0
Os pacotes do ASP.NET Core 2.0 são direcionados ao .NET Standard 2.0. Os pacotes
podem ser referenciados por outras bibliotecas do .NET Standard 2.0 e podem ser
executados em implementações em conformidade com o .NET Standard 2.0, incluindo o
.NET Core 2.0 e o .NET Framework 4.6.1.

O metapacote Microsoft.AspNetCore.All aborda apenas o .Net Core 2.0 porque ele foi
projetado para ser utilizado com o repositório de runtime do .Net Core 2.0.

Atualização da configuração
Uma instância de IConfiguration é adicionada ao contêiner de serviços por padrão no
ASP.NET Core 2.0. 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 Program.cs filtragem no arquivo
em vez de no Startup.cs arquivo. 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 .

Para obter informações sobre o status da documentação planejada, consulte o


problema do GitHub .
atualização de Identity
Facilitamos a criação de APIs Web seguras usando Identity no 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 Autenticação e Identity para 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 sobre como criar um SPA no ASP.NET Core, consulte Usar serviços
JavaScript para criar aplicativos de página única no ASP.NET Core.

Aprimoramentos Kestrel
O Kestrel servidor Web tem novos recursos que o tornam mais adequado como um
servidor voltado para a Internet. Uma série de opções de configuração de restrição de
servidor serão adicionadas na nova propriedade Limits da classe KestrelServerOptions .
Adicione limites para o seguinte:

Número máximo de conexões de cliente


Tamanho máximo do corpo da solicitação
Taxa de dados mínima do corpo da solicitação

Para obter mais informações, consulte Kestrel Implementação de servidor Web em


ASP.NET Core.

WebListener renomeado para HTTP.sys


Os pacotes Microsoft.AspNetCore.Server.WebListener e Microsoft.Net.Http.Server
foram mesclados em um novo pacote Microsoft.AspNetCore.Server.HttpSys . Os
namespaces foram atualizados para corresponderem.

Para obter mais informações, consulte Implementação do servidor Web HTTP.sys no


ASP.NET Core.

Suporte aprimorado a cabeçalho HTTP


Ao usar o MVC para transmitir um FileStreamResult ou um FileContentResult , agora
você tem a opção de definir uma ETag ou uma data LastModified no conteúdo que
você transmitir. Você pode definir esses valores no conteúdo retornado com código
semelhante ao seguinte:

C#

var data = Encoding.UTF8.GetBytes("This is a sample text from a binary


array");
var entityTag = new EntityTagHeaderValue("\"MyCalculatedEtagValue\"");
return File(data, "text/plain", "downloadName.txt", lastModified:
DateTime.UtcNow.AddSeconds(-5), entityTag: entityTag);

O arquivo retornado aos visitantes tem os cabeçalhos HTTP apropriados para os ETag
valores 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ê.

Inicialização de hospedagem e o Application


Insights
Ambientes de hospedagem podem injetar dependências de pacote extras e executar
código durante a inicialização do aplicativo, sem que o aplicativo precise tomar uma
dependência explicitamente ou chamar algum método. Esse recurso pode ser usado
para habilitar determinados ambientes para recursos de "esclarecimento" exclusivos
para esse ambiente sem que o aplicativo precise saber antecipadamente.

No ASP.NET Core 2.0, esse recurso é usado para habilitar o diagnóstico do Application
Insights automaticamente durante a depuração no Visual Studio e (depois de optar por
isto) quando em execução nos Serviços de Aplicativos do Azure. Como resultado, os
modelos de projeto não adicionam mais código e pacotes do Application Insights por
padrão.

Para obter informações sobre o status da documentação planejada, consulte o


problema do GitHub .

Uso automático de tokens antifalsificação


O ASP.NET Core sempre ajudou a fazer a codificação HTML do conteúdo por padrão,
mas com a nova versão é necessário um passo adicional para ajudar a impedir ataques
de XSRF (falsificação de solicitação entre sites). O ASP.NET Core agora emitirá tokens
antifalsificação por padrão e os validará em ações de POST de formulário e em páginas
sem configuração adicional.

Para obter mais informações, consulte Impedir ataques de XSRF/CSRF (solicitação


intersite forjada) no ASP.NET Core.

Pré-compilação automática
Razor A pré-compilação de exibição é 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, consulte Razor exibir compilação e pré-compilação em


ASP.NET Core.

Razor suporte para C# 7.1


O Razor mecanismo de exibição foi atualizado para funcionar com o novo compilador
Roslyn. Isso inclui suporte para recursos do C# 7.1 como expressões padrão, nomes de
tupla inferidos e correspondência de padrões com genéricos. Para usar o C# 7.1 em seu
projeto, adicione a seguinte propriedade no arquivo de projeto e, em seguida,
recarregue a solução:

XML

<LangVersion>latest</LangVersion>

Para obter informações sobre o status dos recursos do C# 7.1, consulte o repositório
GitHub do Roslyn .
Outras atualizações de documentação para 2.0
Perfis de publicação do Visual Studio para a implantação do aplicativo ASP.NET
Core
Gerenciamento de chaves
Configurar a autenticação do Facebook
Configurar a autenticação do Twitter
Configurar a autenticação do Google
Configurar a autenticação da conta da Microsoft

Guia 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 Autenticação e Identity para 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
Artigo • 10/01/2023 • 2 minutos para o fim da leitura

O ASP.NET Core 1.1 inclui os seguintes novos recursos:

Middleware de regravação de URL


Middleware de Cache de Resposta
Componentes de exibição como auxiliares de marcação
Middleware como filtros MVC
CookieProvedor TempData baseado em
Provedor de logs do Serviço de Aplicativo do Azure
Provedor de configuração do Azure Key Vault
Repositórios de chaves da Proteção de Dados de armazenamento do Azure e do
Redis
Servidor WebListener para Windows
Suporte a WebSockets

Escolhendo entre as versões 1.0 e 1.1 do


ASP.NET Core
ASP.NET Core 1.1 tem mais recursos do que ASP.NET Core 1.0. Em geral, recomendamos
o uso da última versão.

Informações adicionais
Notas de versão do ASP.NET Core 1.1.0
Para se conectar ao progresso e aos planos da equipe de desenvolvimento do
ASP.NET Core, fique ligado no ASP.NET Community Standup .
Escolher uma interface do usuário da
Web ASP.NET Core
Artigo • 04/01/2023 • 9 minutos para o fim da leitura

ASP.NET Core é uma estrutura de interface do usuário completa. Escolha quais


funcionalidades combinar que se ajustam às necessidades da interface do usuário da
Web do aplicativo.

Benefícios versus custos da interface do


usuário renderizada por servidor e cliente
Há três abordagens gerais para criar uma interface do usuário da Web moderna com
ASP.NET Core:

Aplicativos que renderizam a interface do usuário do servidor.


Aplicativos que renderizam a interface do usuário no cliente no navegador.
Aplicativos híbridos que aproveitam as abordagens de renderização da interface
do usuário do servidor e do cliente. Por exemplo, a maior parte da interface do
usuário da Web é renderizada no servidor e os componentes renderizados do
cliente são adicionados conforme necessário.

Há benefícios e desvantagens a serem considerados ao renderizar a interface do usuário


no servidor ou no cliente.

Interface do usuário renderizada do servidor


Um aplicativo de interface do usuário da Web que é renderizado no servidor gera
dinamicamente o HTML e o CSS da página no servidor em resposta a uma solicitação de
navegador. A página chega ao cliente pronto para exibição.

Benefícios:

Os requisitos do cliente são mínimos porque o servidor faz o trabalho de geração


de página e lógica:
Ótimo para dispositivos de baixa extremidade e conexões de baixa largura de
banda.
Permite uma ampla variedade de versões do navegador no cliente.
Tempos de carregamento rápidos da página inicial.
Mínimo para nenhum JavaScript para efetuar pull para o cliente.
Flexibilidade de acesso aos recursos do servidor protegido:
Acesso ao banco de dados.
Acesso a segredos, como valores para chamadas à API para o armazenamento
do Azure.
Vantagens da análise de site estático, como a otimização do mecanismo de
pesquisa.

Exemplos de cenários comuns de aplicativos de interface do usuário da Web


renderizados pelo servidor:

Sites dinâmicos, como aqueles que fornecem páginas, dados e formulários


personalizados.
Exiba dados somente leitura, como listas de transações.
Exibir páginas de blog estáticas.
Um sistema de gerenciamento de conteúdo voltado para o público.

Desvantagens:

O custo de computação e o uso de memória estão concentrados no servidor, em


vez de em cada cliente.
As interações do usuário exigem uma viagem de ida e volta ao servidor para gerar
atualizações da interface do usuário.

Interface do usuário renderizada pelo cliente


Um aplicativo renderizado pelo cliente renderiza dinamicamente a interface do usuário
da Web no cliente, atualizando diretamente o DOM do navegador conforme necessário.

Benefícios:

Permite uma interatividade avançada que é quase instantânea, sem a necessidade


de uma viagem de ida e volta para o servidor. A manipulação de eventos e a lógica
da interface do usuário são executadas localmente no dispositivo do usuário com
latência mínima.
Dá suporte a atualizações incrementais, salvando formulários ou documentos
parcialmente concluídos sem que o usuário precise selecionar um botão para
enviar um formulário.
Pode ser projetado para ser executado em um modo desconectado. Atualizações
para o modelo do lado do cliente serão sincronizados novamente com o servidor
depois que uma conexão for restabelecida.
Carga e custo reduzidos do servidor, o trabalho é descarregado para o cliente.
Muitos aplicativos renderizados pelo cliente também podem ser hospedados
como sites estáticos.
Aproveita os recursos do dispositivo do usuário.

Exemplos de interface do usuário da Web renderizada pelo cliente:

Um painel interativo.
Um aplicativo com funcionalidade de arrastar e soltar
Um aplicativo social responsivo e colaborativo.

Desvantagens:

O código para a lógica deve ser baixado e executado no cliente, adicionando ao


tempo de carregamento inicial.
Os requisitos do cliente podem excluir os usuários que têm dispositivos de baixa
extremidade, versões mais antigas do navegador ou conexões de baixa largura de
banda.

Escolher um servidor renderizado ASP.NET Core


solução de interface do usuário
A seção a seguir explica o ASP.NET Core modelos renderizados do servidor de interface
do usuário da Web disponíveis e fornece links para começar. Razor ASP.NET Core Pages
e ASP.NET Core MVC são estruturas baseadas em servidor para criar aplicativos Web
com o .NET.

Razor páginas do ASP.NET Core


Razor Pages é um modelo baseado em página. As preocupações com a interface do
usuário e a lógica de negócios são mantidas separadas, mas dentro da página.
RazorPages é a maneira recomendada de criar novos aplicativos baseados em página ou
em formulário para desenvolvedores novos em ASP.NET Core. RazorO Pages fornece um
ponto de partida mais fácil do que ASP.NET Core MVC.

Razor Benefícios de páginas, além dos benefícios de renderização do servidor:

Crie e atualize rapidamente a interface do usuário. O código da página é mantido


com a página, mantendo as preocupações da interface do usuário e da lógica de
negócios separadas.
Testável e dimensiona para aplicativos grandes.
Mantenha suas páginas ASP.NET Core organizadas de maneira mais simples do
que ASP.NET MVC:
Os modelos de exibição e lógica específicos podem ser mantidos juntos em seu
próprio namespace e diretório.
Grupos de páginas relacionadas podem ser mantidos em seu próprio
namespace e diretório.

Para começar a usar seu primeiro aplicativo ASP.NET Core Razor Pages, consulte
Tutorial: Introdução ao Razor Pages no ASP.NET Core. Para obter uma visão geral
completa do ASP.NET Core Razor Pages, sua arquitetura e benefícios, consulte:
Introdução às Razor páginas no ASP.NET Core.

ASP.NET Core MVC


ASP.NET MVC renderiza a interface do usuário no servidor e usa um padrão de
arquitetura MVC (Model-View-Controller). O padrão MVC separa um aplicativo em três
grupos principais de componentes: Modelos, Exibições e Controladores. As solicitações
do usuário são roteada para um controlador. O controlador é responsável por trabalhar
com o modelo para executar ações do usuário ou recuperar resultados de consultas. O
controlador escolhe a exibição a ser exibida para o usuário e fornece a ele todos os
dados de modelo necessários. O suporte para Razor Pages é criado em ASP.NET Core
MVC.

Benefícios do MVC, além dos benefícios de renderização do servidor:

Com base em um modelo escalonável e maduro para a criação de aplicativos Web


grandes.
Separação clara de preocupações com a máxima flexibilidade.
A separação de responsabilidades do Model-View-Controller garante que o
modelo de negócios possa evoluir sem ser firmemente acoplado a detalhes de
implementação de baixo nível.

Para começar a usar ASP.NET Core MVC, consulte Introdução ao ASP.NET Core MVC.
Para obter uma visão geral da arquitetura e dos benefícios do ASP.NET Core MVC,
confira Visão geral do ASP.NET Core MVC.

Blazor Server
O Blazor é uma estrutura para criar uma interface do usuário web interativa do lado do
cliente com o .NET:

Crie interfaces do usuário interativas avançadas usando C# em vez de JavaScript .


Compartilhe a lógica de aplicativo do lado do cliente e do servidor gravada no
.NET.
Renderize a interface do usuário, como HTML e CSS para suporte amplo de
navegadores, incluindo navegadores móveis.
Integre-se a plataformas de hospedagem modernas, como o Docker.
Crie aplicativos móveis e de área de trabalho híbrida com .NET e Blazor.

Usar o .NET para desenvolvimento web do lado do cliente oferece as seguintes


vantagens:

escreva o código em C# em vez de JavaScript.


Aproveite o ecossistema .NET existente das bibliotecas .NET.
Compartilhe a lógica de aplicativo entre o servidor e o cliente.
Beneficie-se com o desempenho, confiabilidade e segurança do .NET.
Mantenha-se produtivo no Windows, Linux ou macOS com um ambiente de
desenvolvimento, como o Visual Studio ou o Visual Studio Code .
Crie um conjunto comum de linguagens, estruturas e ferramentas que são estáveis,
com recursos avançados e fáceis de usar.

Blazor Serverfornece suporte para hospedar a interface do usuário renderizada pelo


servidor em um aplicativo ASP.NET Core. As atualizações da interface do usuário do
cliente são tratadas em uma SignalR conexão. O runtime permanece no servidor e
manipula a execução do código C# do aplicativo.

Para obter mais informações, consulte modelos de hospedagem ASP.NET Core Blazor e
ASP.NET CoreBlazor. O modelo de hospedagem renderizado pelo Blazor cliente é
descrito na Blazor WebAssembly seção posteriormente neste artigo.

Escolher uma solução de ASP.NET Core


renderizada pelo cliente
A seção a seguir explica brevemente os modelos renderizados do cliente de interface do
usuário da Web ASP.NET Core disponíveis e fornece links para começar.

Blazor WebAssembly
Blazor WebAssembly é uma estrutura spa (aplicativo de página única) para criar
aplicativos Web interativos do lado do cliente com as características gerais descritas na
Blazor Server seção anterior neste artigo.

A execução do código do .NET em navegadores da Web é possibilitada por


WebAssembly (abreviado como wasm ). O WebAssembly é um formato de código de
bytes compacto, otimizado para download rápido e máxima velocidade de execução. O
WebAssembly é um padrão aberto da Web compatível com navegadores da Web sem
plug-ins. Blazor WebAssembly funciona em todos os navegadores da Web modernos,
incluindo os navegadores móveis.

Quando um Blazor WebAssembly aplicativo é criado e executado:

Os arquivos de código C# e do Razor são compilados em assemblies do .NET.


Os assemblies e o runtime do .NET são baixados no navegador.
O Blazor WebAssembly inicializa o runtime do .NET e o configura para carregar os
assemblies no aplicativo. O Blazor WebAssembly runtime usa a interoperabilidade
JavaScript para manipular a manipulação do DOM (Modelo de Objeto de
Documento) e as chamadas à API do navegador.

Para obter mais informações, consulte ASP.NET Core Blazor e modelos de hospedagem
ASP.NET CoreBlazor. O modelo de hospedagem renderizado pelo Blazor servidor é
descrito na Blazor Server seção anterior neste artigo.

ASP.NET Core SPA (Aplicativo de Página Única) com


Estruturas JavaScript, como Angular e React
Crie lógica do lado do cliente para aplicativos ASP.NET Core usando estruturas
JavaScript populares, como Angular ou React . ASP.NET Core fornece modelos de
projeto para Angular e React e também pode ser usado com outras estruturas
JavaScript.

Benefícios de ASP.NET Core SPA com Estruturas JavaScript, além dos benefícios de
renderização do cliente listados anteriormente:

O ambiente de runtime do JavaScript já é fornecido com o navegador.


Grande comunidade e ecossistema maduro.
Crie lógica do lado do cliente para aplicativos ASP.NET Core usando estruturas
popularesJS, como Angular e React.

Desvantagens:

Mais linguagens de codificação, estruturas e ferramentas necessárias.


Difícil compartilhar código para que alguma lógica possa ser duplicada.

Para começar. confira:

Usar Angular com ASP.NET Core


Usar React com ASP.NET Core
Escolha uma solução híbrida: ASP.NET Core
MVC ou Razor Pages plusBlazor
MVC, Razor Pages e Blazor fazem parte da estrutura ASP.NET Core e foram projetados
para serem usados juntos. Razoros componentes podem ser integrados aos Razor
aplicativos Pages e MVC em uma solução ou Blazor Server hospedadaBlazor
WebAssembly. Quando uma exibição ou página é renderizada, os componentes podem
ser pré-gerados ao mesmo tempo.

Benefícios para MVC ou Razor Pages mais Blazor, além dos benefícios do MVC ou Razor
do Pages:

A pré-geração executa componentes Razor no servidor e os renderiza em uma


exibição ou página, o que melhora o tempo de carga percebido do aplicativo.
Adicione interatividade a exibições ou páginas existentes com o Auxiliar de Marca
de Componente.

Para começar a usar ASP.NET Core MVC ou Razor Pages mais Blazor, confira Pré-gerar e
integrar componentes ASP.NET CoreRazor.

Próximas etapas
Para obter mais informações, consulte:

ASP.NET Core Blazor


Modelos de hospedagem do ASP.NET Core Blazor
Pré-renderizar e integrar componentes Razor do ASP.NET Core
Comparar serviços gRPC com APIs HTTP
Tutorial: criar um aplicativo Web Razor
Pages com o ASP.NET Core
Artigo • 02/12/2022 • 2 minutos para o fim da leitura

Esta série de tutoriais explica as noções básicas sobre a criação de um aplicativo Web
Razor Pages.

Para ver uma introdução mais avançada voltada para desenvolvedores familiarizados
com controladores e exibições, confira Introdução ao Razor Pages no ASP.NET Core.

Se não estiver familiarizado com o desenvolvimento de ASP.NET Core e não tiver


certeza de qual solução de interface do usuário da Web do ASP.NET Core atenderá
melhor às suas necessidades, consulte Escolher uma interface do usuário do ASP.NET
Core.

Esta série inclui os seguintes tutoriais:

1. Criar um aplicativo Web Razor Pages


2. Adicionar um modelo a um aplicativo Razor Pages
3. Scaffold (gerar) páginas Razor
4. Trabalhar com um banco de dados
5. Atualizar páginas 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 ao Razor Pages no
ASP.NET Core
Artigo • 02/12/2022 • 24 minutos para o fim da leitura

De Rick Anderson

Este é o primeiro tutorial de uma série que ensina os conceitos básicos da criação de um
aplicativo Web ASP.NET Core Razor Pages.

Para obter uma introdução mais avançada voltada para desenvolvedores que estão
familiarizados com controladores e exibições, consulte Introdução às Razor páginas.
Para obter uma introdução em vídeo, consulte Entity Framework Core para iniciantes .

Se não estiver familiarizado com o desenvolvimento de ASP.NET Core e não tiver


certeza de qual solução de interface do usuário da Web do ASP.NET Core atenderá
melhor às suas necessidades, consulte Escolher uma interface do usuário do ASP.NET
Core.

No final deste tutorial, você terá um Razor aplicativo Web Pages que gerencia um banco
de dados de filmes.

Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.
Criar um aplicativo Web Razor Pages
Visual Studio

Inicie o Visual Studio e selecione Criar um projeto.

Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core


Aplicativo> WebAvançar.

Na caixa de diálogo Configurar seu novo projeto , insira RazorPagesMovie


para Nome do projeto. É importante nomear o projeto RazorPagesMovie,
incluindo a correspondência da capitalização, para que os namespaces
correspondam quando você copiar e colar o código de exemplo.

Selecione Avançar.

Na caixa de diálogo Informações adicionais:


Selecione .NET 7.0.
Verificar: não usar instruções de nível superior está desmarcada .

Selecione Criar.
O seguinte projeto inicial é criado:

Para obter abordagens alternativas para criar o projeto, consulte Criar um novo
projeto no Visual Studio.
Execute o aplicativo
Visual Studio

Selecione RazorPagesMovie no Gerenciador de Soluções e pressione Ctrl+F5 para


executar sem o depurador.

O Visual Studio exibe a seguinte caixa de diálogo quando um projeto ainda não
está configurado para usar o SSL:

Selecione Sim se você confia no certificado SSL do IIS Express.

A seguinte caixa de diálogo é exibida:

Selecione Sim se você concordar com confiar no certificado de desenvolvimento.


Para obter informações sobre como confiar no navegador Firefox, consulte Firefox
SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

Visual Studio:

Executa o aplicativo, que inicia o Kestrel servidor.


Inicia o navegador padrão em https://localhost:<port> , que exibe a
interface do usuário dos aplicativos. <port> é a porta aleatória atribuída
quando o aplicativo foi criado.

Examinar os arquivos de projeto


As seções a seguir contêm uma visão geral das pastas e arquivos principais do projeto
com os quais você trabalhará em tutoriais posteriores.

Pasta Páginas
Contém Razor páginas e arquivos de suporte. Cada Razor página é um par de arquivos:

Um .cshtml arquivo que tem marcação HTML com código C# usando Razor
sintaxe.
Um .cshtml.cs arquivo que tem código C# que manipula eventos de página.

Arquivos de suporte têm nomes que começam com um sublinhado. Por exemplo, o
_Layout.cshtml arquivo configura elementos de interface do usuário comuns a todas as
páginas. _Layout.cshtml configura o menu de navegação na parte superior da página e
o aviso de direitos autorais na parte inferior da página. Saiba mais em Layout no
ASP.NET Core.

Pasta wwwroot
Contém ativos estáticos, como arquivos HTML, arquivos JavaScript e arquivos CSS. Saiba
mais em Arquivos estáticos no ASP.NET Core.

appsettings.json

Contém dados de configuração, como cadeias de conexão. Para obter mais informações,
consulte Configuração no ASP.NET Core.
Module.vb
Contém o seguinte código:

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

As seguintes linhas de código neste arquivo criam um WebApplicationBuilder com


padrões pré-configurados, adicionam Razor suporte ao Pages ao contêiner di (injeção
de dependência) e criam o aplicativo:

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

A página de exceção do desenvolvedor é habilitada por padrão e fornece informações


úteis sobre exceções. Os aplicativos de produção não devem ser executados no modo
de desenvolvimento porque a página de exceção do desenvolvedor pode vazar
informações confidenciais.

O código a seguir define o ponto de extremidade de exceção como /Error e habilita o


protocolo HSTS quando o aplicativo não está em execução no modo de
desenvolvimento:

C#

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

Por exemplo, o código anterior é executado quando o aplicativo está no modo de


produção ou teste. Para obter mais informações, confira Usar vários ambientes no
ASP.NET Core.

O código a seguir habilita vários middlewares:

app.UseHttpsRedirection(); : redireciona solicitações HTTP para HTTPS.

app.UseStaticFiles(); : permite que arquivos estáticos, como HTML, CSS,


imagens e JavaScript sejam atendidos. Saiba mais em Arquivos estáticos no
ASP.NET Core.
app.UseRouting(); : adiciona a correspondência de rotas ao pipeline de
middleware. Para obter mais informações, consulte Roteamento no ASP.NET Core
app.MapRazorPages(); : configura o roteamento de ponto de extremidade para
Razor o Pages.
app.UseAuthorization(); : autoriza um usuário a acessar recursos seguros. Esse

aplicativo não usa autorização, portanto, essa linha pode ser removida.
app.Run(); : executa o aplicativo.

Solução de problemas com o exemplo


concluído
Se você encontrar um problema que não possa resolver, compare seu código com o
projeto concluído. Exibir ou baixar o projeto concluído (como baixar).
Próximas etapas
Próximo: Adicionar um modelo
Parte 2, adicione um modelo a um
Razor aplicativo Pages no ASP.NET Core
Artigo • 08/12/2022 • 48 minutos para o fim da leitura

Neste tutorial, classes são adicionadas para gerenciar filmes em um banco de dados. As
classes de modelo do aplicativo usam o Entity Framework Core (EF Core) para trabalhar
com o banco de dados. EF Core é um mapeador relacional de objeto (O/RM) que
simplifica o acesso a dados. Você escreve as classes de modelo primeiro e EF Core cria o
banco de dados.

As classes de modelo são conhecidas como classes POCO (de "Plain-O ld CLR Objects")
porque não têm uma dependência de EF Core. Elas definem as propriedades dos dados
que são armazenados no banco de dados.

Adicionar um modelo de dados


Visual Studio

1. Em Gerenciador de Soluções, clique com o botão direito do mouse no


Razorprojeto> PagesMovie Adicionar>Nova Pasta. Nomeie a pasta Models .

2. Clique com o botão direito do mouse na Models pasta. Selecione


Adicionar>Classe. Dê à classe o nome Movie.

3. Adicione as seguintes propriedades à classe Movie :

C#

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; }
}
A classe Movie contém:

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

Um atributo [DataType] que especifica o tipo de dados na ReleaseDate


propriedade . Com esse atributo:
O usuário não precisa inserir informações de hora no campo de data.
Somente a data é exibida, não as informações de tempo.

O ponto de interrogação após string indica que a propriedade é anulável.


Para obter mais informações, confira Tipos de referência anuláveis.

DataAnnotations são abordados em um tutorial posterior.

Crie o projeto para verificar se não há erros de compilação.

Fazer scaffold do modelo de filme


Nesta seção, é feito o scaffold do modelo de filme. Ou seja, a ferramenta de scaffolding
gera páginas para operações de CRUD (Criar, Ler, Atualizar e Excluir) para o modelo do
filme.

Visual Studio

1. Crie a pasta Páginas/Filmes :


a. Clique com o botão direito do mouse na pasta >PáginasAdicionar>Nova
Pasta.
b. Nomeie a pasta Filmes.

2. Clique com o botão direito do mouse na pasta


>Páginas/FilmesAdicionar>Novo Item Com Scaffolded.
3. Na caixa de diálogo Adicionar Novo Scaffold , selecione Razor Páginas
usando o CRUD (Entity Framework)>Adicionar.
4. Conclua a caixa de diálogo Adicionar Razor Páginas usando o CRUD (Entity
Framework ):
a. Na lista suspensa Classe de modelo, selecione Filme
(RazorPagesMovie.Models).
b. Na linha Classe de contexto de dados, selecione o sinal de + (adição).
i. Na caixa de diálogo Adicionar Contexto de Dados , o nome
RazorPagesMovie.Data.RazorPagesMovieContext da classe é gerado.
c. Selecione Adicionar.

O appsettings.json arquivo é atualizado com a cadeia de conexão usada para se


conectar a um banco de dados local.

Arquivos criados e atualizados


O processo de scaffold cria os arquivos a seguir:

Pages/Movies: Create, Delete, Details, Edit e Index.


Data/RazorPagesMovieContext.cs

Os arquivos criados são explicados no próximo tutorial.

O processo de scaffold adiciona o seguinte código realçado ao Program.cs arquivo:

Visual Studio

C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

As Program.cs alterações são explicadas posteriormente neste tutorial.

Criar o esquema de banco de dados inicial


usando o recurso de migração do EF
O recurso de migrações no Entity Framework Core fornece uma maneira de:

Crie o esquema inicial do banco de dados.


Atualize incrementalmente o esquema de banco de dados para mantê-lo em
sincronia com o modelo de dados do aplicativo. Os dados existentes no banco de
dados são preservados.
Visual Studio

Nesta seção, a janela PMC ( Console do Gerenciador de Pacotes ) é usada para:

Adicionar uma migração inicial.


Atualize o banco de dados com a migração inicial.

1. No menu Ferramentas selecione Gerenciador de Pacotes NuGet>Console do


Gerenciador de Pacotes.

2. No PMC, insira os seguintes comandos:

PowerShell

Add-Migration InitialCreate
Update-Database

Os comandos anteriores:

Instale as ferramentas mais recentes do Entity Framework Core depois de


desinstalar qualquer versão anterior, se ela existir.
Execute o migrations comando para gerar o código que cria o esquema inicial do
banco de dados.

O seguinte aviso é exibido, que é abordado em uma etapa posterior:


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

O comando migrations gera código para criar o esquema de banco de dados inicial. O
esquema é baseado no modelo especificado em DbContext . 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 update comando executa o Up método em migrações que não foram aplicadas.


Nesse caso, update executa o Up método no Migrations/<time-
stamp>_InitialCreate.cs arquivo , que cria o banco de dados.

Examinar o contexto registrado com a injeção de


dependência
O ASP.NET Core é construído com a injeção de dependência. Serviços, como o contexto
do EF Core banco de dados, são registrados com injeção de dependência durante a
inicialização do aplicativo. Componentes que exigem esses serviços (como Razor Pages)
são fornecidos por meio de parâmetros de construtor. O código de construtor que
obtém uma instância de contexto do banco de dados é mostrado mais adiante no
tutorial.

A ferramenta de scaffolding criou automaticamente um contexto de banco de dados e o


registrou com o contêiner de injeção de dependência. O código realçado a seguir é
adicionado ao Program.cs arquivo pelo scaffolder:

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O contexto RazorPagesMovieContext de dados:

Deriva de Microsoft. EntityFrameworkCore.DbContext.


Especifica quais entidades estão incluídas no modelo de dados.
EF Core Coordena a funcionalidade, como Criar, Ler, Atualizar e Excluir, para o
Movie modelo.

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Data
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext
(DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}
public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; } =
default!;
}
}

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 desenvolvimento local, o sistema de
configuração lê a cadeia de conexão do appsettings.json arquivo.

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

Se você receber o seguinte erro:

Console

SqlException: Cannot open database "RazorPagesMovieContext-GUID"


requested by the login. The login failed.
Login failed for user 'User-name'.

Você perdeu a etapa de migrações.

2. Teste o link Criar Novo .


7 Observação

Talvez você não consiga inserir casas decimais ou vírgulas no campo Price .
Para dar suporte à validação do jQuery para localidades 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 .

3. Teste os links Editar, Detalhes e Excluir.

O tutorial a seguir explica os arquivos criados por scaffolding.

Solução de problemas com o exemplo


concluído
Se você encontrar um problema que não possa resolver, compare seu código com o
projeto concluído. Exibir ou baixar o projeto concluído (como baixar).

Recursos adicionais
Anterior: Introdução ao Próximo: Páginas com Scaffolded Razor
Parte 3, páginas scaffolded Razor no
ASP.NET Core
Artigo • 02/12/2022 • 26 minutos para o fim da leitura

De Rick Anderson

Este tutorial examina as Razor Páginas criadas por scaffolding no tutorial anterior.

As páginas Create, Delete, Details e Edit


Examine o Pages/Movies/Index.cshtml.cs modelo de página:

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies;

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

public IList<Movie> Movie { get;set; } = default!;

public async Task OnGetAsync()


{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}

Razor As páginas são derivadas de PageModel. Por convenção, a PageModel classe


derivada é chamada PageNameModel de . Por exemplo, a página Índice é chamada
IndexModel de .
O construtor usa injeção de dependência para adicionar o RazorPagesMovieContext à
página:

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

Confira Código assíncrono para obter mais informações sobre a programação


assíncrona com o Entity Framework.

Quando uma GET solicitação é feita para a página, o OnGetAsync método retorna uma
lista de filmes para a Razor Página. Em uma Razor Página, OnGetAsync ou OnGet é
chamado 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 , nenhuma instrução return é
usada. Por exemplo, examine a Privacy Página:

C#

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

namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;

public PrivacyModel(ILogger<PrivacyModel> logger)


{
_logger = logger;
}

public void OnGet()


{
}
}
}
Quando o tipo de retorno for IActionResult ou Task<IActionResult> , é necessário
fornecer uma instrução de retorno. Por exemplo, o Pages/Movies/Create.cshtml.cs
OnPostAsync método :

C#

public async Task<IActionResult> OnPostAsync()


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

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

return RedirectToPage("./Index");
}

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

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

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

Razor pode fazer a transição de HTML para C# ou para Razormarcação específica.


Quando um @ símbolo é seguido por uma Razor palavra-chave reservada, ele faz a
transição para Razoruma marcação específica, caso contrário, ele faz a transição para
C#.

A diretiva @page
A @page Razor diretiva torna o arquivo uma ação MVC, o que significa que ele pode lidar
com solicitações. @page deve ser a primeira diretiva do Razor em uma página. @page e
@model são exemplos de transição para Razormarcação específica. Consulte Razor

sintaxe para obter mais informações.

A diretiva @model
CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel
A @model diretiva especifica o tipo do modelo passado para a Razor Página. No exemplo
anterior, a @model linha disponibiliza a PageModel classe derivada para a Razor Página. O
modelo é usado nos auxiliares HTML @Html.DisplayNameFor e @Html.DisplayFor na
página.

Examine a expressão lambda usada no auxiliar HTML a seguir:

CSHTML

@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á violação de acesso quando
model , model.Movie ou model.Movie[0] está ou está null vazio. Quando a expressão
lambda é avaliada, por exemplo, com @Html.DisplayFor(modelItem => item.Title) , os
valores de propriedade do modelo são avaliados.

A página de layout
Selecione os links Razorde menu PagesMovie, Homee Privacy. Cada página mostra o
mesmo layout de menu. O layout do menu é implementado no
Pages/Shared/_Layout.cshtml arquivo.

Abra e examine o Pages/Shared/_Layout.cshtml arquivo.

Os modelos de layout permitem que o layout do contêiner HTML seja:

Especificado em um único lugar.


Aplicado a várias páginas no site.

Localize a linha @RenderBody() . RenderBody é um espaço reservado em que todas as


exibições específicas da página são mostradas, encapsuladas na página de layout. Por
exemplo, selecione o Privacy link e a exibição Pages/Privacy.cshtml é renderizada
dentro do RenderBody método .

ViewData e layout
Considere a seguinte marcação do Pages/Movies/Index.cshtml arquivo:

CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel

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

A marcação realçada anterior é um exemplo de Razor transição para C#. Os caracteres


{ e } circunscrevem um bloco de código C#.

A PageModel classe base contém uma ViewData propriedade de dicionário que pode ser
usada para passar dados para um Modo de Exibição. Os objetos são adicionados ao
ViewData dicionário usando um padrão de valor de chave . No exemplo anterior, a

propriedade Title é adicionada ao dicionário ViewData .

A Title propriedade é usada no Pages/Shared/_Layout.cshtml arquivo . A marcação a


seguir mostra as primeiras linhas do _Layout.cshtml arquivo.

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
<link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-
version="true" />

A linha @*Markup removed for brevity.*@ é um Razor comentário. Ao contrário dos


comentários <!-- --> HTML, Razor os comentários não são enviados ao cliente. Confira
Documentos da Web do MDN: Introdução ao HTML para obter mais informações.

Atualizar o layout
1. Altere o <title> elemento no Pages/Shared/_Layout.cshtml arquivo para exibir
Movie em vez de RazorPagesMovie.

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

2. Localize o seguinte elemento de âncora no Pages/Shared/_Layout.cshtml arquivo.

CSHTML

<a class="navbar-brand" asp-area="" asp-


page="/Index">RazorPagesMovie</a>

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

CSHTML

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

O elemento de âncora anterior é um Auxiliar de Marcas. Nesse caso, ele é o


Auxiliar de Marcas de Âncora. O asp-page="/Movies/Index" atributo e o valor do
Auxiliar de Marca criam um link para a /Movies/Index Razor Página. O valor do
atributo asp-area está vazio e, portanto, a área não é usada no link. Confira Áreas
para obter mais informações.

4. Salve as alterações e teste o aplicativo selecionando o link RpMovie . Confira o


arquivo _Layout.cshtml no GitHub caso tenha problemas.

5. Teste os Homelinks , RpMovie, Criar, Editar e Excluir . Cada página define o título,
que você pode ver na guia do navegador. Quando você marca uma página, o
título é usado para o indicador.

7 Observação

Talvez você não consiga inserir casas decimais ou vírgulas no campo Price . Para
dar suporte à validação jQuery para localidades não inglesas que usam uma
vírgula ("") para um ponto decimal e formatos de data não US-English, você deve
tomar medidas para globalizar o aplicativo. Confira Problema 4076 do GitHub
para obter instruções sobre como adicionar casas decimais.

A Layout propriedade é definida no Pages/_ViewStart.cshtml arquivo:


CSHTML

@{
Layout = "_Layout";
}

A marcação anterior define o arquivo de layout como Pages/Shared/_Layout.cshtml para


todos os Razor arquivos na pasta Páginas . Veja Layout para obter mais informações.

O modelo Criar página


Examine o modelo de Pages/Movies/Create.cshtml.cs página:

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext
_context;

public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext
context)
{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

[BindProperty]
public Movie Movie { get; set; } = default!;

// To protect from overposting attacks, see


https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie ==
null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

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

O método OnGet inicializa qualquer estado necessário para a página. A página Criar não
tem nenhum estado para inicializar, assim, Page é retornado. Apresentamos um
exemplo de inicialização de estado OnGet posteriormente no tutorial. O Page método
cria um PageResult objeto que renderiza a Create.cshtml página.

A Movie propriedade usa o atributo [BindProperty] para aceitar a associação de modelo.


Quando o formulário Criar posta os valores de formulário, o runtime do ASP.NET Core
associa os valores postados ao modelo Movie .

O método OnPostAsync é executado quando a página posta dados de formulário:

C#

public async Task<IActionResult> OnPostAsync()


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

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

return RedirectToPage("./Index");
}

Se há algum erro de modelo, o formulário é reexibido juntamente com quaisquer dados


de formulário postados. A maioria dos erros de modelo podem ser capturados no lado
do cliente antes do formulário ser enviado. Um exemplo de um erro de modelo é
postar, para o campo de data, um valor que não pode ser convertido em uma data. A
validação do lado do cliente e a validação de modelo são abordadas mais adiante no
tutorial.

Se não houver erros de modelo:

Os dados são salvos.


O navegador é redirecionado para a página Índice.
A página Criar Razor
Examine o Pages/Movies/Create.cshtml Razor arquivo de página:

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

O Visual Studio exibe as marcas a seguir em uma fonte em negrito diferente usada
em auxiliares de marcações:

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

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

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 Razor marcação para cada campo no modelo, exceto a
ID, semelhante à seguinte:

CSHTML

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


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

Os Auxiliares de Marca de Validação ( <div asp-validation-summary e <span asp-


validation-for ) exibem erros de validação. A validação será abordada em mais detalhes
posteriormente nesta série.

O Auxiliar de Marca de Rótulo ( <label asp-for="Movie.Title" class="control-label">


</label> ) gera a legenda do rótulo e [for] o atributo para a Title propriedade .

O Auxiliar de Marca de Entrada ( <input asp-for="Movie.Title" class="form-control"> )


usa os atributos DataAnnotations e produz atributos HTML necessários para a Validação
de jQuery no lado do cliente.

Para obter mais informações sobre Auxiliares de Marcas, como <form method="post"> ,
confira Auxiliares de Marcas no ASP.NET Core.

Recursos adicionais
Anterior: Adicionar um modelo Avançar: Trabalhar com um banco de dados
Parte 4 da série de tutoriais em Razor
Páginas
Artigo • 03/12/2022 • 22 minutos para o fim da leitura

Por Joe Audette

O objeto RazorPagesMovieContext cuida da tarefa de se conectar ao banco de dados e


mapear objetos Movie para registros do banco de dados. O contexto do banco de
dados é registrado com o contêiner injeção de dependência em Program.cs :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

O sistema de configuração de ASP.NET Core lê a ConnectionString chave. Para


desenvolvimento local, a configuração obtém a cadeia de conexão do appsettings.json
arquivo.

Visual Studio

A cadeia de conexão gerada é semelhante ao seguinte JSON:

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"RazorPagesMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=RazorPagesMovieContext-
bc;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Quando o aplicativo é implantado em um servidor de teste ou produção, uma variável


de ambiente pode ser usada para definir a cadeia de conexão como um servidor de
banco de dados de teste ou de produção. Para obter mais informações, confira
Configuração.

Visual Studio

SQL Server Express LocalDB


O LocalDB é uma versão leve do mecanismo de banco de dados do SQL Server
Express direcionada para o desenvolvimento de programas. O LocalDB é iniciado
sob demanda e executado no modo de usuário e, portanto, não há nenhuma
configuração complexa. Por padrão, o banco de dados LocalDB cria arquivos *.mdf
no diretório C:\Users\<user>\ .

1. No menu Exibir, abra SSOX (Pesquisador de Objetos do SQL Server).


2. Clique com o botão direito do mouse na Movie tabela 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.

3. Clique com o botão direito do mouse na Movie tabela e selecione Exibir


Dados:

Propagar o banco de dados


Crie uma classe chamada SeedData na pasta Models com o seguinte código:

C#
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;

namespace RazorPagesMovie.Models;

public static class SeedData


{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new RazorPagesMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<RazorPagesMovieContext>>()))
{
if (context == null || context.Movie == null)
{
throw new ArgumentNullException("Null
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 filmes no banco de dados, o inicializador de semente retornará e nenhum


filme será adicionado.

C#

if (context.Movie.Any())
{
return;
}

Adicionar o inicializador de semeadura


Atualize o Program.cs com o seguinte código realçado:

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

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


{
var services = scope.ServiceProvider;

SeedData.Initialize(services);
}
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

No código anterior, Program.cs foi modificado para fazer o seguinte:

Obtenha uma instância de contexto de banco de dados do contêiner de DI


(injeção de dependência).
Chame o seedData.Initialize método , passando para ele a instância de contexto
do banco de dados.
Descarte o contexto quando o método de semente for concluído. A instrução
using garante que o contexto seja descartado.

A seguinte exceção ocorre quando Update-Database não foi executada:

SqlException: Cannot open database "RazorPagesMovieContext-" requested by the


login. The login failed. Login failed for user 'user name'.

Testar o aplicativo
Exclua todos os registros no banco de dados para que o método de semente seja
executado. Interrompa e inicie o aplicativo para propagar o banco de dados. Se o banco
de dados não for propagado, coloque um ponto if (context.Movie.Any()) de
interrupção e percorra o código.

O aplicativo mostra os dados propagados:


Recursos adicionais
Anterior: Scaffolded Razor Páginas a seguir: Atualizar as páginas
Parte 5, atualize as páginas geradas em
um aplicativo ASP.NET Core
Artigo • 03/12/2022 • 15 minutos para o fim da leitura

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação
não é ideal. ReleaseDate deve ser duas palavras, Data de Lançamento.

Atualizar o modelo
Atualize Models/Movie.cs com o seguinte código realçado:

C#

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

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
}

No código anterior:

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.
O atributo [Display] especifica o nome de exibição de um campo. No código
anterior, Release Date em vez de ReleaseDate .
O atributo [DataType] especifica o tipo dos dados ( Date ). As informações de
tempo armazenadas no campo não são exibidas.

DataAnnotations é abordado no próximo tutorial.

Navegue até Páginas/Filmes e passe o mouse sobre um link Editar para ver a URL de
destino.
Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marca de Âncora no
Pages/Movies/Index.cshtml arquivo.

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 Auxiliar de Marca de Âncora gera dinamicamente o valor do


atributo HTML href da Razor Página (a rota é relativa), o asp-page e o identificador de
rota (). asp-route-id Para obter mais informações, consulte Geração de URL para
Páginas.

Use Exibir Fonte de um navegador para examinar a marcação gerada. Uma parte do
HTML gerado é mostrada abaixo:

HTML

<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 do filme com uma cadeia de caracteres de
consulta . Por exemplo, o ?id=1 em https://localhost:5001/Movies/Details?id=1 .

Adicionar modelo de rota


Atualize as páginas Editar, Detalhes e Excluir Razor para usar o {id:int} modelo de
rota. 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:

HTML

<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 {id:int} modelo de rota que não inclui o inteiro
retorna um erro HTTP 404 (não encontrado). Por exemplo,
https://localhost:5001/Movies/Details retorna um erro 404. Para tornar a ID opcional,
acrescente ? à restrição de rota:

CSHTML

@page "{id:int?}"

Teste o comportamento de @page "{id:int?}" :

1. Defina a diretiva de página em como Pages/Movies/Details.cshtml @page "


{id:int?}" .

2. Defina um ponto de interrupção em public async Task<IActionResult>


OnGetAsync(int? id) , em Pages/Movies/Details.cshtml.cs .

3. Navegue até https://localhost:5001/Movies/Details/ .

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 OnGetAsync
método retorna NotFound (HTTP 404):

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

Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

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

Examinar o tratamento de exceção de simultaneidade


Examine o OnPostAsync método no Pages/Movies/Edit.cshtml.cs arquivo:

C#

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
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");
}

private bool MovieExists(int id)


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

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 :

1. Defina um ponto de interrupção em catch (DbUpdateConcurrencyException)


2. Selecione Editar para um filme, faça alterações, mas não insira Salvar.
3. Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em
seguida, exclua o filme.
4. 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 Pages/Movies/Edit.cshtml.cs arquivo:

C#

public class EditModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

[BindProperty]
public Movie Movie { get; set; } = default!;

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


{
if (id == null || _context.Movie == null)
{
return NotFound();
}

var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id ==


id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}

// To protect from overposting attacks, enable the specific properties


you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
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");
}

private bool MovieExists(int id)


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

Quando uma solicitação HTTP GET é feita na página Filmes/Editar, por exemplo,
https://localhost:5001/Movies/Edit/3 :

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


O Page método renderiza a Pages/Movies/Edit.cshtml Razor Página. O
Pages/Movies/Edit.cshtml arquivo contém a diretiva @model
RazorPagesMovie.Pages.Movies.EditModel de modelo , que disponibiliza o modelo

de filme na página.
O formulário Editar é exibido com os valores do filme.

Quando a página Movies/Edit é postada:


Os valores de formulário na página são associados à propriedade Movie . O
atributo [BindProperty] habilita o Model binding.

C#

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

Se houver erros no estado do modelo, por exemplo, ReleaseDate não poderão ser
convertidos em uma data, o formulário será reproduzido com os valores enviados.

Se não houver erros do modelo, o filme será salvo.

Os métodos HTTP GET nas páginas Index, Create e Delete Razor seguem um padrão
semelhante. O método HTTP POST OnPostAsync na página Criar Razor segue um padrão
semelhante ao OnPostAsync método na Página de Edição Razor .

Recursos adicionais
Anterior: Trabalhar com um banco de dados Avançar: Adicionar pesquisa
Parte 6, adicionar pesquisa ao ASP.NET
Core Razor Pages
Artigo • 10/01/2023 • 10 minutos para o fim da leitura

De Rick Anderson

Nas seções a seguir, a pesquisa de filmes por gênero ou nome é adicionada.

Adicione o código realçado a seguir a Pages/Movies/Index.cshtml.cs :

C#

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

namespace RazorPagesMovie.Pages.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext
_context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext
context)
{
_context = context;
}

public IList<Movie> Movie { get;set; } = default!;


[BindProperty(SupportsGet = true)]
public string ? SearchString { get; set; }
public SelectList ? Genres { get; set; }
[BindProperty(SupportsGet = true)]
public string ? MovieGenre { get; set; }

No código anterior:

SearchString : contém o texto que os usuários inserem na caixa de texto de


pesquisa. SearchString tem o [BindProperty] atributo . [BindProperty] associa
valores de formulário e cadeias de consulta ao mesmo nome da propriedade.
[BindProperty(SupportsGet = true)] é necessário para associação em solicitações

HTTP 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 selecionado pelo usuário. Por exemplo,

"Western".
Genres e MovieGenre são abordados mais adiante neste tutorial.

2 Aviso

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 a solicitações GET , defina a propriedade


SupportsGet do atributo [BindProperty] como true :

C#

[BindProperty(SupportsGet = true)]

Para obter mais informações, confira ASP.NET Core Community Standup: Bind on
GET discussion (YouTube) .

Atualize o método OnGetAsync da página de Índice pelo seguinte código:

C#

public async Task OnGetAsync()


{
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}

Movie = await movies.ToListAsync();


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

C#

// using System.Linq;
var movies = from m in _context.Movie
select m;

A consulta só é definida neste ponto, 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:

C#

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 operador de
consulta padrão, como o método Where ou Contains . As consultas LINQ não são
executadas quando são definidas ou quando são modificadas chamando um método,
como Where , Contains ou OrderBy . Em vez disso, a execução da consulta é adiada. A
avaliação de uma expressão é atrasada até que seu valor realizado seja iterado ou o
ToListAsync método seja chamado. Consulte Execução de consulta para obter mais

informações.

7 Observação

O Contains método é 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. SQLite com a ordenação padrão é uma
combinação de diferenciação de maiúsculas de minúsculas emaiúsculas de
minúsculas, dependendo da consulta. Para obter informações sobre como tornar as
consultas SQLite que não diferenciam maiúsculas de minúsculas, consulte o
seguinte:

Este problema do GitHub


Este problema do GitHub
Ordenações e diferenciação de maiúsculas e minúsculas

Navegue até a página Filmes e acrescente uma cadeia de caracteres 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 Índice, a cadeia de caracteres de


pesquisa poderá ser passada como um segmento de URL. Por exemplo,
https://localhost:5001/Movies/Ghost .

CSHTML

@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 runtime 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 ). A associação de modelo não diferencia


maiúsculas de minúsculas.

No entanto, não é esperado 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 Pages/Movies/Index.cshtml arquivo e adicione a marcação realçada no seguinte


código:

CSHTML

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

A marca <form> HTML usa os seguintes Auxiliares de Marcas:

Auxiliar de Marca de Formulário. Quando o formulário é enviado, a cadeia de


caracteres de filtro é enviada para a página Pages/Movies/Index por meio da cadeia
de consulta.
Auxiliar de marcação de entrada

Salve as alterações e teste o filtro.

Pesquisar por gênero


Atualize o método OnGetAsync da página de Índice pelo seguinte código:

C#

public async Task OnGetAsync()


{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

var movies = from m in _context.Movie


select m;

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

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

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

C#

// Use LINQ to get list of genres.


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

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

C#

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

Adicionar pesquisa por gênero à Razor Página


Atualize o Index.cshtml <form> elemento conforme realçado na seguinte marcação:

CSHTML

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

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

Anterior: Atualizar as páginas Avançar: Adicionar um novo campo


Parte 7, adicione um novo campo a uma
Razor Página no ASP.NET Core
Artigo • 02/12/2022 • 24 minutos para o fim da leitura

De Rick Anderson

Nesta seção, as Migrações do Entity Framework Code First são usadas para:

Adicionar um novo campo ao modelo.


Migrar a nova alteração de esquema de campo para o banco de dados.

Ao usar o EF Code First para criar e acompanhar automaticamente um banco de dados,


Code First:

Adiciona uma __EFMigrationsHistory tabela ao banco de dados para acompanhar


se o esquema do banco de dados está em sincronia com as classes de modelo das
quais ele foi gerado.
Gerará uma exceção se as classes de modelo não estiverem sincronizadas com o
banco de dados.

A verificação automática de que o esquema e o modelo estão em sincronia facilita a


localização de problemas de código de banco de dados inconsistentes.

Adicionando uma propriedade de classificação


ao modelo de filme
1. Abra o Models/Movie.cs arquivo e adicione uma Rating propriedade:

C#

public class Movie


{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;

[Display(Name = "Release Date")]


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

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
public string Rating { get; set; } = string.Empty;
}
2. Edite Pages/Movies/Index.cshtml e adicione um Rating campo:

CSHTML

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

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

3. Atualize as seguintes páginas com um Rating campo:

Pages/Movies/Create.cshtml .
Pages/Movies/Delete.cshtml .
Pages/Movies/Details.cshtml .
Pages/Movies/Edit.cshtml .

O aplicativo não funcionará até que o banco de dados seja atualizado para incluir o
novo campo. A execução do aplicativo sem uma atualização no banco de dados lança
um SqlException :

SqlException: Invalid column name 'Rating'.

A SqlException exceção é causada pela classe de modelo Movie atualizada ser diferente
do esquema da tabela Movie do banco de dados. Não há nenhuma Rating coluna na
tabela do 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 os
desenvolvedores evoluam rapidamente o modelo e o esquema de banco de dados
juntos. A desvantagem é que os dados existentes no banco de dados são perdidos.
Não use essa abordagem em um banco de dados de produção. Descartar o banco
de dados em alterações de esquema e usar um inicializador para propagar
automaticamente o banco de dados com dados de teste geralmente é 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 é manter 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 exemplo é mostrada abaixo, mas faça essa alteração para cada new Movie
bloco.

C#

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

Consulte o arquivo SeedData.cs concluído .

Compile a solução.

Visual Studio

Adicionar uma migração para o campo de classificação


1. No menu Ferramentas , selecione Console do Gerenciador de Pacotes do
Gerenciador de Pacotes > NuGet.
2. No PMC, insira os seguintes comandos:

PowerShell

Add-Migration Rating
Update-Database

O comando Add-Migration informa à estrutura:

Compare o Movie modelo com o esquema de Movie banco de dados.


Crie código para migrar o esquema de banco de dados 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.

O Update-Database comando informa à estrutura para aplicar as alterações de


esquema ao banco de dados e preservar os dados existentes.

Exclua todos os registros no banco de dados, o inicializador propagará o banco de


dados e incluirá o Rating campo . A exclusão pode ser feita com os links de
exclusão no navegador ou no Sql Server Pesquisador de Objetos (SSOX).

Outra opção é excluir o banco de dados e usar as migrações para recriar o banco
de dados. Para excluir o banco de dados no SSOX:

1. Selecione o banco de dados no SSOX.

2. Clique com o botão direito do mouse no banco de dados e selecione Excluir.

3. Marque Fechar conexões existentes.

4. Selecione OK.

5. No PMC, atualize o banco de dados:

PowerShell

Update-Database

Execute o aplicativo e verifique se você pode criar, editar e exibir filmes com um Rating
campo. Se o banco de dados não for propagado, defina um ponto de interrupção no
método SeedData.Initialize .
Recursos adicionais
Anterior: Adicionar Pesquisa Avançar: Adicionar Validação
Parte 8 da série de tutoriais no Razor
Pages
Artigo • 03/12/2022 • 31 minutos para o fim da leitura

De Rick Anderson

Nesta seção, a lógica de validação é adicionada para o modelo Movie . As regras de


validação são impostas sempre que um usuário cria ou edita um filme.

Validação
Um princípio-chave do desenvolvimento de software é chamado DRY (“Don't Repeat
Yourself”). Razor O Pages incentiva o desenvolvimento em que a funcionalidade é
especificada uma vez e é 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 à validação fornecido pelo Pages e pelo Razor Entity Framework é um bom
exemplo do princípio DRY:

As regras de validação são especificadas declarativamente em um só lugar, na


classe de modelo.
As regras são impostas em todos os lugares do aplicativo.

Adicionar regras de validação ao modelo de


filme
O System.ComponentModel.DataAnnotations namespace fornece:

Um conjunto de atributos de validação internos que são aplicados


declarativamente a uma classe ou propriedade.
A formatação de atributos como [DataType] esse ajuda na formatação e não
fornece nenhuma validação.

Atualize a classe Movie para aproveitar os atributos de validação [Required] ,


[StringLength] , [RegularExpression] e [Range] internos.

C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; } = string.Empty;

// [Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { 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; } = string.Empty;

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

Os atributos de validação especificam o comportamento a ser imposto nas


propriedades de modelo às quais eles são aplicados:

Os atributos [Required] e [MinimumLength] indicam que uma propriedade deve ter


um valor. Nada impede que um usuário insira espaço em branco para atender a
essa validação.

O atributo [RegularExpression] é usado para limitar quais caracteres podem ser


inseridos. No código anterior, Genre :
Deve usar apenas letras.
A primeira letra deve ser maiúscula. Espaços em branco são permitidos
enquanto números e caracteres especiais não são permitidos.

O RegularExpression Rating :
Exige que o primeiro caractere seja uma letra maiúscula.
Permite caracteres especiais e números nos espaços subsequentes. "PG-13" é
válido para uma classificação, mas falha para um Genre .

O atributo [Range] restringe um valor a um intervalo especificado.

O [StringLength] atributo pode definir um comprimento máximo de uma


propriedade de cadeia de caracteres e, opcionalmente, seu comprimento mínimo.

Tipos de valor, como decimal , int , float , DateTime , são inerentemente


necessários e não precisam do [Required] atributo .

As regras de validação anteriores são usadas para demonstração, elas não são ideais
para um sistema de produção. Por exemplo, o anterior impede a entrada de um filme
com apenas dois caracteres e não permite caracteres especiais em Genre .

Ter regras de validação automaticamente impostas pelo ASP.NET Core ajuda a:

Torne o aplicativo mais robusto.


Reduza as chances de salvar dados inválidos no banco de dados.

Interface do usuário do erro de validação em Razor


páginas
Execute o aplicativo e navegue para Pages/Movies.

Selecione o link Criar Novo . Preencha o formulário com alguns valores inválidos.
Quando a validação do lado do cliente do jQuery detecta o erro, ela exibe uma
mensagem de erro.
7 Observação

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. Consulte
este comentário do GitHub 4076 para obter instruções sobre como adicionar
vírgula decimal.

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 JavaScript e jQuery e no lado do servidor, quando um usuário tem o
JavaScript desabilitado.

Um benefício significativo é que nenhuma alteração de código foi necessária nas


páginas Criar ou Editar. Depois que as anotações de dados foram aplicadas ao modelo,
a interface do usuário de validação foi habilitada. As Razor Páginas criadas neste tutorial
pegaram automaticamente as regras de validação usando atributos de validação nas
propriedades da Movie classe de modelo. 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


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

1. Desabilite o JavaScript no navegador. O JavaScript pode ser desabilitado usando as


ferramentas de desenvolvedor do navegador. Se o JavaScript não puder ser
desabilitado no navegador, tente outro navegador.

2. Defina um ponto de interrupção no método OnPostAsync da página Criar ou Editar.

3. Envie um formulário com dados inválidos.

4. Verifique se o estado do modelo é inválido:


C#

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

Como alternativa, desabilite a validação do lado do cliente no servidor.

O código a seguir mostra uma parte da Create.cshtml página estruturada


anteriormente no tutorial. Ele é usado pelas páginas Criar e Editar para:

Exiba o formulário inicial.


Reproduz o formulário em caso de erro.

CSHTML

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

O Auxiliar de Marcação de Entrada usa os atributos de DataAnnotations e produz os


atributos HTML necessários para a Validação do jQuery no lado do cliente. O Auxiliar de
Marcação de Validação exibe erros de validação. Consulte Validação para obter mais
informações.

As páginas Criar e Editar não têm nenhuma regra de validação. As regras de validação e
as cadeias de caracteres de erro são especificadas somente na classe Movie . Essas regras
de validação são aplicadas automaticamente às Razor Páginas que editam o Movie
modelo.

Quando a lógica de validação precisa ser alterada, ela é feita apenas no modelo. A
validação é aplicada consistentemente em todo o aplicativo, a lógica de validação é
definida em um só lugar. A validação em um único lugar ajuda a manter o código limpo
e facilita sua manutenção e atualização.

Usar atributos DataType


Examine a classe Movie . O namespace System.ComponentModel.DataAnnotations fornece
atributos de formatação, além do conjunto interno de atributos de validação. O atributo
[DataType] é aplicado às propriedades ReleaseDate e Price .

C#

// [Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

Os [DataType] atributos fornecem:

Dicas para o mecanismo de exibição formatar os dados.


Fornece atributos como <a> para URLs e <a href="mailto:EmailAddress.com">
para 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. [DataType] atributos não são atributos de validação. No


aplicativo de exemplo, somente a data é exibida, sem hora.

A DataType enumeração fornece muitos tipos de dados, como Date , Time , PhoneNumber ,
Currency , EmailAddress e muito mais.

Os [DataType] atributos:

Pode habilitar o aplicativo para fornecer automaticamente recursos específicos de


tipo. Por exemplo, um link mailto: pode ser criado para DataType.EmailAddress .
Pode fornecer um seletor DataType.Date de data em navegadores que dão suporte
a HTML5.
Emita HTML 5 data- , pronunciado "data dash", atributos que os navegadores
HTML 5 consomem.
Não forneça 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:

C#

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


true)]
public DateTime ReleaseDate { get; set; }

A ApplyFormatInEditMode configuração especifica que a formatação será aplicada


quando o valor for exibido para edição. Esse comportamento pode não ser desejado
para alguns campos. Por exemplo, em valores de moeda, o símbolo de moeda
geralmente não é desejado 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 em
vez de como renderizá-los em uma tela. O [DataType] atributo fornece os seguintes
benefícios que não estão disponíveis com [DisplayFormat] :

O navegador pode habilitar recursos HTML5, por exemplo, para mostrar um


controle de calendário, o símbolo de moeda apropriado à localidade, links de
email etc.
Por padrão, o navegador renderiza dados usando o formato correto com base em
sua 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 o [Range] atributo 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:

C#

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

É uma prática recomendada evitar a compilação de datas difíceis em modelos, portanto,


usar o [Range] atributo e DateTime não é recomendável. Use Configuração para
intervalos de datas e outros valores que estão sujeitos a alterações frequentes em vez
de especificá-la no código.

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

C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


public string Title { get; set; } = string.Empty;

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


public DateTime ReleaseDate { get; set; }

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


public string Genre { get; set; } = string.Empty;

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


[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

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

Introdução ao Razor Páginas e EF Core mostra operações avançadas EF Core com Razor
o Pages.

Aplicar migrações
As DataAnnotations aplicadas à classe alteram o esquema. Por exemplo, as
DataAnnotations aplicadas ao campo Title :

C#

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; } = string.Empty;

Limitam os caracteres a 60.


Não permitem um valor null .

Atualmente a tabela Movie tem o seguinte esquema:

SQL
CREATE TABLE [dbo].[Movie] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (MAX) NULL,
[ReleaseDate] DATETIME2 (7) NOT NULL,
[Genre] NVARCHAR (MAX) NULL,
[Price] DECIMAL (18, 2) NOT NULL,
[Rating] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED ([ID] ASC)
);

As alterações do esquema anterior não fazem com que o EF lance uma exceção. No
entanto, crie uma migração de forma que o esquema seja consistente com o modelo.

Visual Studio

No menu Ferramentas , selecione Console do Gerenciador de Pacotes do


Gerenciador de Pacotes > NuGet. No PMC, insira os seguintes comandos:

PowerShell

Add-Migration New_DataAnnotations
Update-Database

Update-Database executa os métodos Up da classe New_DataAnnotations . Examine o


método Up :

C#

public partial class NewDataAnnotations : Migration


{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Movie",
type: "nvarchar(60)",
maxLength: 60,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");

migrationBuilder.AlterColumn<string>(
name: "Rating",
table: "Movie",
type: "nvarchar(5)",
maxLength: 5,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");

migrationBuilder.AlterColumn<string>(
name: "Genre",
table: "Movie",
type: "nvarchar(30)",
maxLength: 30,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
}

A tabela Movie atualizada tem o seguinte esquema:

SQL

CREATE TABLE [dbo].[Movie] (


[ID] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (60) NOT NULL,
[ReleaseDate] DATETIME2 (7) NOT NULL,
[Genre] NVARCHAR (30) NOT NULL,
[Price] DECIMAL (18, 2) NOT NULL,
[Rating] NVARCHAR (5) NOT NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED ([ID] ASC)
);

Publicar no Azure
Para obter informações sobre como implantar no Azure, confira Tutorial: Criar um
aplicativo ASP.NET Core no Azure com Banco de Dados SQL.

Obrigado por concluir esta introdução ao Razor Pages. Introdução ao Razor Pages e EF
Core é um excelente acompanhamento para este tutorial.

Recursos adicionais
Auxiliares de marcação 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

Anterior: adicionar um novo campo


Introdução ao ASP.NET Core MVC
Artigo • 15/11/2022 • 21 minutos para o fim da leitura

De Rick Anderson

Este tutorial ensina a usar o desenvolvimento Web do ASP.NET Core MVC com
controladores e exibições. Se você não estiver familiarizado com ASP.NET Core
desenvolvimento na Web, considere a Razor versão páginas deste tutorial, que fornece
um ponto de partida mais fácil. Consulte Escolher um ASP.NET Core interface do
usuário, que compara Razor Pages, MVC e Blazor para desenvolvimento de interface do
usuário.

Este é o primeiro tutorial de uma série que ensina ASP.NET Core desenvolvimento da
Web do MVC com controladores e exibições.

No final da série, você terá um aplicativo que gerencia e exibe dados de filme. Você
aprenderá como:

" Crie um aplicativo Web.


" Adicionar e gerar o scaffolding de um modelo.
" Trabalhar com um banco de dados.
" Adicionar pesquisa e validação.

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

Pré-requisitos
Visual Studio

Versão prévia mais recente do Visual Studio 2022 com a carga de trabalho
de ASP.NET e desenvolvimento web .

Criar um aplicativo Web


Visual Studio

Inicie o Visual Studio e selecione Criar um projeto.


Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core
Aplicativo Web (Model-View-Controller)>Avançar.
Na caixa de diálogo Configurar seu novo projeto , insira MvcMovie para Nome
do projeto. É importante nomear o projeto MvcMovie. A capitalização precisa
corresponder a cada namespace um quando o código é copiado.
Selecione Avançar.
Na caixa de diálogo Informações adicionais:
Selecione .NET 7.0.
Verifique se Não usar instruções de nível superior está desmarcado .
Selecione Criar.

Para obter mais informações, incluindo abordagens alternativas para criar o projeto,
consulte Criar um novo projeto no Visual Studio.

O Visual Studio usa o modelo de projeto padrão para o projeto MVC criado. O
projeto criado:

É um aplicativo funcional.
É um projeto inicial básico.

Executar o aplicativo

Visual Studio
Selecione Ctrl+F5 para executar o aplicativo sem o depurador.

O Visual Studio exibe a seguinte caixa de diálogo quando um projeto ainda


não está configurado para usar o SSL:

Selecione Sim se você confia no certificado SSL do IIS Express.

A seguinte caixa de diálogo é exibida:

Selecione Sim se você concordar com confiar no certificado de


desenvolvimento.

Para obter informações sobre como confiar no navegador Firefox, consulte


Firefox SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

O Visual Studio executa o aplicativo e abre o navegador padrão.


A barra de endereços mostra localhost:<port#> e não algo como example.com . O
nome do host padrão para seu computador local é localhost . Uma porta aleatória
é usada para o servidor Web quando o Visual Studio cria um projeto Web.

Iniciar o aplicativo sem depuração selecionando Ctrl+F5 permite:

Realize alterações de código.


Salve o arquivo.
Atualize rapidamente o navegador e veja as alterações de código.

Você pode iniciar o aplicativo no modo de depuração ou não depuração no menu


Depurar :

Você pode depurar o aplicativo selecionando o botão https na barra de


ferramentas :

A imagem a seguir mostra o aplicativo:


Visual Studio

Ajuda do Visual Studio


Saiba como depurar código C# usando o Visual Studio
Introdução ao IDE do Visual Studio

No próximo tutorial desta série, você aprenderá sobre o MVC e começará a escrever
algum código.

Próximo: Adicionar um controlador


Parte 2, adicione um controlador a um
aplicativo MVC ASP.NET Core
Artigo • 02/12/2022 • 19 minutos para o fim da leitura

De Rick Anderson

O padrão de arquitetura MVC (Model-View-Controller) separa um aplicativo em três


componentes principais: Model, View e Controller. O padrão MVC ajuda a criar
aplicativos que são mais testáveis e fáceis de atualizar comparado aos aplicativos
monolíticos tradicionais.

Os aplicativos baseados no MVC contêm:

Models: classes que representam os dados do aplicativo. As classes de modelo


usam a lógica de validação para impor regras de negócio aos dados.
Normalmente, os objetos de modelo recuperam e armazenam o estado do
modelo em um banco de dados. Neste tutorial, um modelo Movie recupera dados
de filmes de um banco de dados, fornece-os para a exibição ou atualiza-os. O
dados atualizados são gravados em um banco de dados.
Views: exibições são os componentes que exibem a interface do usuário do
aplicativo. Em geral, essa interface do usuário exibe os dados de modelo.
Controladores C: classes que:
Lidar com solicitações de navegador.
Recuperar dados do modelo.
Modelos de exibição de chamada que retornam uma resposta.

Em um aplicativo MVC, a exibição exibe apenas informações. O controlador manipula e


responde à entrada e interação do usuário. Por exemplo, o controlador manipula
segmentos de URL e valores de cadeia de caracteres de consulta e passa esses valores
para o modelo. O modelo pode usar esses valores para consultar o banco de dados. Por
exemplo:

https://localhost:5001/Home/Privacy : especifica o Home controlador e a ação

Privacy .

https://localhost:5001/Movies/Edit/5 : é uma solicitação para editar o filme com


ID=5 usando o Movies controlador e a ação Edit , que são detalhados
posteriormente no tutorial.

Os dados de rota são explicados posteriormente no tutorial.


O padrão de arquitetura MVC separa um aplicativo em três grupos principais de
componentes: Modelos, Exibições e Controladores. Esse padrão ajuda a alcançar a
separação de preocupações: a lógica da interface do usuário pertence à exibição. A
lógica de entrada pertence ao controlador. A lógica de negócios pertence ao modelo.
Essa separação ajuda a gerenciar a complexidade ao criar um aplicativo, pois permite
trabalhar em um aspecto da implementação de cada vez sem afetar o código de outro.
Por exemplo, você pode trabalhar no código de exibição sem depender do código da
lógica de negócios.

Esses conceitos são introduzidos e demonstrados nesta série de tutoriais ao criar um


aplicativo de filme. O projeto MVC contém pastas para os Controladores e as Exibições.

Adicionar um controlador
Visual Studio

Em Gerenciador de Soluções, clique com o botão direito do mouse em


Controladores > Adicionar > Controlador.
Na caixa de diálogo Adicionar Novo Item Scaffolded, selecione Controlador MVC
– Adicionar Vazio>.

Na caixa de diálogo Adicionar Novo Item – MvcMovie , insira


HelloWorldController.cs e selecione Adicionar.

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

C#

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

namespace MvcMovie.Controllers;

public class HelloWorldController : Controller


{
//
// GET: /HelloWorld/
public string Index()
{
return "This is my default action...";
}
//
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}

Cada método public em um controlador pode ser chamado como um ponto de


extremidade HTTP. Na amostra acima, ambos os métodos retornam uma cadeia de
caracteres. Observe os comentários que precedem cada método.

Um ponto de extremidade HTTP:

É uma URL de destino no aplicativo Web, como


https://localhost:5001/HelloWorld .

Combina:
O protocolo usado: HTTPS .
O local de rede do servidor Web, incluindo a porta TCP: localhost:5001 .
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. Posteriormente no tutorial, o
mecanismo de scaffolding é usado para gerar HTTP POST métodos, que atualizam os
dados.

Execute o aplicativo sem o depurador.

Acrescente "HelloWorld" ao caminho na barra de endereços. O método Index retorna


uma cadeia de caracteres.

O MVC invoca classes de 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 qual código invocar:
/[Controller]/[ActionName]/[Parameters]

O formato de roteamento é definido no Program.cs arquivo .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Quando você navega até o aplicativo e não fornece segmentos de URL, ele usa como
padrão o controlador "Home" e o método "Index" especificado na linha de modelo
realçada acima. Nos segmentos de URL anteriores:

O primeiro segmento de URL determina a classe do controlador a ser executada.


Portanto, localhost:5001/HelloWorld mapeia para a classe Controlador
HelloWorld .
A segunda parte do segmento de URL determina o método de ação na classe.
Portanto, localhost:5001/HelloWorld/Index faz com que o Index método da
HelloWorldController classe seja executado. Observe que você precisou apenas
navegar para localhost:5001/HelloWorld e o método Index foi chamado por
padrão. 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 até: https://localhost:{PORT}/HelloWorld/Welcome . Substitua {PORT} pelo


número da porta.

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.

C#

// 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 para proteger o aplicativo contra entradas HtmlEncoder.Default.Encode mal-


intencionadas, como por meio do JavaScript.
Usa Cadeias de caracteres interpoladas em $"Hello {name}, NumTimes is:
{numTimes}" .

Execute o aplicativo e navegue até: https://localhost:{PORT}/HelloWorld/Welcome?


name=Rick&numtimes=4 . Substitua {PORT} pelo número da porta.

Experimente valores diferentes para name e numtimes na URL. O sistema de associação


de modelo MVC mapeia automaticamente os parâmetros nomeados da cadeia de
caracteres de consulta para os parâmetros no método . Consulte Model binding para
obter mais informações.

Na imagem anterior:

O segmento Parameters de URL não é usado.


Os name parâmetros e numTimes são passados na cadeia de caracteres de
consulta .
O ? (ponto de interrogação) na URL acima é um separador e a cadeia de
caracteres de consulta segue.
O & caractere separa pares campo-valor.

Substitua o método Welcome pelo seguinte código:

C#

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


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

Execute o aplicativo e insira a seguinte URL: https://localhost:


{PORT}/HelloWorld/Welcome/3?name=Rick

Na URL anterior:

O terceiro segmento de URL correspondeu ao parâmetro id de rota .


O método Welcome contém um parâmetro id que correspondeu ao modelo de
URL no método MapControllerRoute .
O à direita inicia a cadeia de ? caracteres de consulta .

C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

No exemplo anterior:

O terceiro segmento de URL correspondeu ao parâmetro id de rota .


O método Welcome contém um parâmetro id que correspondeu ao modelo de
URL no método MapControllerRoute .
O ? à direita (em id? ) indica que o parâmetro id é opcional.

Anterior: Introdução ao Próximo: Adicionar um Modo de Exibição


Parte 3, adicionar uma exibição a um
aplicativo MVC ASP.NET Core
Artigo • 02/12/2022 • 25 minutos para o fim da leitura

De Rick Anderson

Nesta seção, você modificará a HelloWorldController classe para usar arquivos de


exibição Razor . Isso encapsula corretamente o processo de geração de respostas HTML
para um cliente.

Os modelos de exibição são criados usando Razor. RazorModelos de exibição baseados


em:

Ter uma .cshtml extensão de arquivo.


Forneça uma maneira elegante de criar uma saída HTML com C#.

Atualmente, o Index método retorna uma cadeia de caracteres com uma mensagem na
classe de controlador. Na classe HelloWorldController , substitua o método Index pelo
seguinte código:

C#

public IActionResult Index()


{
return View();
}

O código anterior:

Chama o método do View controlador.


Usa um modelo de exibição para gerar uma resposta HTML.

Métodos do controlador:

São chamados de métodos de ação. Por exemplo, o método de Index ação no


código anterior.
Geralmente, retorna um IActionResult ou uma classe derivada de ActionResult, não
um tipo como string .

Adicionar uma exibição


Visual Studio

Clique com o botão direito do mouse na pasta Exibições e em Adicionar > Nova
Pasta e nomeie a pasta HelloWorld.

Clique com o botão direito do mouse na pasta Views/HelloWorld e, em seguida, em


Adicionar > Novo Item.

Na caixa de diálogo Adicionar Novo Item – MvcMovie :

Na caixa de pesquisa no canto superior direito, insira exibição


Selecionar Razor Exibição – Vazio
Mantenha o valor da caixa Nome , Index.cshtml .
Selecione Adicionar

Substitua o conteúdo do arquivo de Views/HelloWorld/Index.cshtml Razor exibição pelo


seguinte:

CSHTML

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

<h2>Index</h2>

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


Acesse o diretório https://localhost:{PORT}/HelloWorld :

O Index método na HelloWorldController executou a instrução return View(); ,


que especificou que o método deve usar um arquivo de modelo de exibição para
renderizar uma resposta ao navegador.

Um nome de arquivo de modelo de exibição não foi especificado, portanto, o MVC


usa o arquivo de exibição padrão como padrão. Quando o nome do arquivo de
exibição não é especificado, o modo de exibição padrão é retornado. A exibição
padrão tem o mesmo nome que o método de ação, Index neste exemplo. O
modelo de exibição /Views/HelloWorld/Index.cshtml é usado.

A imagem a seguir mostra a cadeia de caracteres "Olá do nosso Modelo de


Exibição!" embutida em código no modo de exibição:

Alterar exibições e páginas de layout


Selecione os links de menu MvcMovie, Homee Privacy. Cada página mostra o mesmo
layout de menu. O layout do menu é implementado no Views/Shared/_Layout.cshtml
arquivo .

Abra o arquivo Views/Shared/_Layout.cshtml .

Os modelos de layout permitem:

Especificando o layout de contêiner HTML de um site em um só lugar.


Aplicando o layout do contêiner HTML em várias páginas no site.
Localize a linha @RenderBody() . RenderBody é um espaço reservado em que todas as
páginas específicas à exibição criadas são mostradas, encapsuladas na página de layout.
Por exemplo, se você selecionar o Privacy link, a exibição Views/Home/Privacy.cshtml
será renderizada dentro do RenderBody método .

Alterar o título, o rodapé e o link de menu no


arquivo de layout
Substitua o conteúdo do Views/Shared/_Layout.cshtml arquivo pela marcação a seguir.
As alterações são realçadas:

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie App</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
</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-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Movies"
asp-action="Index">Movie App</a>
<button class="navbar-toggler" type="button" data-bs-
toggle="collapse" data-bs-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
justify-content-between">
<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>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2022 - Movie App - <a asp-area="" asp-controller="Home"
asp-action="Privacy">Privacy</a>
</div>
</footer>
<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>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

A marcação anterior fez as seguintes alterações:

Três ocorrências de MvcMovie para Movie App .


O elemento de âncora <a class="navbar-brand" asp-area="" asp-
controller="Home" asp-action="Index">MvcMovie</a> para <a class="navbar-brand"

asp-controller="Movies" asp-action="Index">Movie App</a> .

Na marcação anterior, o atributo auxiliar de marca de âncora e o asp-area="" valor do


atributo foram omitidos porque este aplicativo não está usando Áreas.

Observação: o Movies controlador não foi implementado. Neste ponto, o Movie App
link não é funcional.

Salve as alterações e selecione o Privacy link. Observe como o título na guia do


navegador exibe Privacy Política – Aplicativo de Filme em vez de Privacy Política –
MvcMovie
Selecione o link Home.

Observe que o título e o texto de âncora exibem o Aplicativo de Filme. As alterações


foram feitas uma vez no modelo de layout e todas as páginas no site refletem o novo
texto do link e o novo título.

Examine o Views/_ViewStart.cshtml arquivo:

CSHTML

@{
Layout = "_Layout";
}

O Views/_ViewStart.cshtml arquivo traz o Views/Shared/_Layout.cshtml arquivo para


cada exibição. A propriedade Layout pode ser usada para definir outra exibição de
layout ou defina-a como null para que nenhum arquivo de layout seja usado.

Abra o Views/HelloWorld/Index.cshtml arquivo de exibição.

Altere o título e <h2> o elemento conforme realçado no seguinte:

CSHTML

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

<h2>My Movie List</h2>

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

O título e <h2> o elemento são ligeiramente diferentes, portanto, fica claro 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:

CSHTML

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

Salve as alterações e navegue para https://localhost:{PORT}/HelloWorld .

Observe que os seguintes foram alterados:

Título do navegador.
Título primário.
Títulos secundários.

Se não houver alterações no navegador, poderá ser armazenado em cache o conteúdo


que está sendo exibido. Pressione Ctrl+F5 no navegador para forçar a resposta do
servidor a ser carregada. O título do navegador é criado com ViewData["Title"] o que
definimos no Index.cshtml modelo de exibição e o "- Aplicativo de Filme" adicional
adicionado no arquivo de layout.

O conteúdo no modelo de exibição Index.cshtml é mesclado com o modelo de


exibição Views/Shared/_Layout.cshtml . Uma única resposta HTML é enviada ao
navegador. Os modelos de layout facilitam a realização de alterações que se aplicam a
todas as páginas de um aplicativo. Para saber mais, confira Layout.

No entanto, o pequeno bit de "dados", a mensagem "Olá do nosso Modelo de


Exibição!", é embutido em código. O aplicativo MVC tem um "V" (exibição), um "C"
(controlador), mas nenhum "M" (modelo) ainda.
Passando dados do controlador para a exibição
As ações do controlador são invocadas em resposta a uma solicitação de URL de
entrada. Uma classe de controlador é o local em que o código é escrito e que manipula
as solicitações recebidas do navegador. O controlador recupera dados de uma fonte de
dados e decide qual tipo de resposta será enviada novamente para o navegador.
Modelos de exibição podem ser usados em um controlador para gerar e formatar uma
resposta HTML para o navegador.

Os controladores são responsáveis por fornecer os dados necessários para que um


modelo de exibição renderize uma resposta.

Os modelos de exibição não devem:

Fazer lógica de negócios


Interagir diretamente com um banco de dados.

Um modelo de exibição deve funcionar apenas com os dados fornecidos a ele pelo
controlador. Manter essa "separação de preocupações" ajuda a manter o código:

Limpo.
Testável.
Sustentável.

Atualmente, o Welcome método na classe usa um name parâmetro e ID e, em


HelloWorldController seguida, gera os valores diretamente para o navegador.

Em vez de fazer com que o controlador renderize a resposta como uma cadeia de
caracteres, altere o controlador para que ele use um modelo de exibição. O modelo de
exibição gera uma resposta dinâmica, o que significa que os dados apropriados devem
ser passados do controlador para a exibição para gerar a resposta. Faça isso fazendo
com que o controlador coloque os dados dinâmicos (parâmetros) de que o modelo de
exibição precisa em um ViewData dicionário. O modelo de exibição pode acessar os
dados dinâmicos.

Em HelloWorldController.cs , altere o Welcome método para adicionar um Message valor


e NumTimes ao ViewData dicionário.

O ViewData dicionário é um objeto dinâmico, o que significa que qualquer tipo pode ser
usado. O ViewData objeto não tem propriedades definidas até que algo seja adicionado.
O sistema de associação de modelo MVC mapeia automaticamente os parâmetros
nomeados e numTimes da cadeia de caracteres name de consulta para os parâmetros no
método . O completo HelloWorldController :
C#

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

namespace MvcMovie.Controllers;

public class HelloWorldController : Controller


{
public IActionResult Index()
{
return View();
}
public IActionResult Welcome(string name, int numTimes = 1)
{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;
return View();
}
}

O objeto de dicionário ViewData contém dados que serão passados para a exibição.

Crie um modelo de exibição de 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:

CSHTML

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

<h2>Welcome</h2>

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

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

https://localhost:{PORT}/HelloWorld/Welcome?name=Rick&numtimes=4
Os dados são obtidos da URL e passados para o controlador usando o associador de
modelo MVC. O controlador empacota os dados em um dicionário ViewData e passa
esse objeto para a exibição. Em seguida, a exibição renderiza os dados como HTML para
o navegador.

No exemplo anterior, o ViewData dicionário 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 do modelo de
exibição para passar dados é preferencial em relação à abordagem de ViewData
dicionário.

No próximo tutorial, será criado um banco de dados de filmes.

Anterior: Adicionar um controlador em seguida: Adicionar um modelo


Parte 4, adicionar um modelo a um
aplicativo MVC ASP.NET Core
Artigo • 06/12/2022 • 60 minutos para o fim da leitura

Por Rick Anderson e Jon P Smith .

Neste tutorial, classes são adicionadas para gerenciar filmes em um banco de dados.
Essas classes são a parte "Model" do aplicativo de VC M.

Essas classes de modelo são usadas com o Entity Framework Core (EF Core) para
trabalhar com um banco de dados. EF Core é uma estrutura orm (mapeamento
relacional de objeto) que simplifica o código de acesso a dados que você precisa
escrever.

As classes de modelo criadas são conhecidas como classes POCO , de Plain Old CLR
Objects. As classes POCO não têm nenhuma dependência em EF Core. Eles definem
apenas as propriedades dos dados a serem armazenados no banco de dados.

Neste tutorial, as classes de modelo são criadas primeiro e EF Core criam o banco de
dados.

Adicionar uma classe de modelo de dados


Visual Studio

Clique com o botão direito do mouse na pasta >ModelosAdicionar>Classe. Atribua


um nome ao arquivo Movie.cs .

Atualize o Models/Movie.cs arquivo com o seguinte código:

C#

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

A classe Movie contém um campo Id , que é exigido pelo banco de dados para a chave
primária.

O DataType atributo em ReleaseDate especifica o tipo dos dados ( Date ). Com esse
atributo:

O usuário não precisa inserir informações de hora no campo de data.


Somente a data é exibida, não as informações de tempo.

DataAnnotations são abordados em um tutorial posterior.

O ponto de interrogação após string indica que a propriedade é anulável. Para obter
mais informações, confira Tipos de referência anuláveis.

Adicionar pacotes NuGet


Visual Studio

No menu Ferramentas, selecione Gerenciador de Pacotes NuGet>Console do


Gerenciador de Pacotes (PMC).

No PMC, execute os seguintes comandos:

PowerShell
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.SqlServer

Os comandos anteriores adicionam:

O EF Core provedor de SQL Server. O pacote do provedor instala o EF Core


pacote como uma dependência.
Os utilitários usados pelos pacotes instalados automaticamente na etapa de
scaffolding, posteriormente no tutorial.

Compile o projeto como uma verificação de erros do compilador.

Aplicar scaffold a páginas de filme


Use a ferramenta de scaffolding para produzir Create páginas CRUD (, Read Update , e
Delete ) para o modelo de filme.

Visual Studio

Em Gerenciador de Soluções, clique com o botão direito do mouse na pasta


Controladores e selecione Adicionar > Novo Item Com Scaffolded.

Na caixa de diálogo Adicionar Novo Item Scaffolded , selecione Controlador MVC


com exibições, usando Entity Framework > Add.
Conclua a caixa de diálogo Adicionar Controlador MVC com exibições usando o
Entity Framework :

Na lista suspensa Classe de modelo, selecione Filme (MvcMovie.Models).


Na linha Classe de contexto de dados, selecione o sinal de + (adição).
Na caixa de diálogo Adicionar Contexto de Dados , o nome da classe
MvcMovie.Data.MvcMovieContext é gerado.
Selecione Adicionar.
Exibições e Nome do controlador: mantenha o padrão.
Selecione Adicionar.
Se você receber uma mensagem de erro, selecione Adicionar uma segunda vez
para tentar novamente.

O scaffolding atualiza o seguinte:

Insere as referências de pacote necessárias no arquivo de MvcMovie.csproj


projeto.
Registra o contexto do banco de dados no Program.cs arquivo .
Adiciona uma cadeia de conexão de banco de dados ao appsettings.json
arquivo.

O scaffolding cria o seguinte:

Um controlador de filmes: Controllers/MoviesController.cs


Razor exibir arquivos para páginas Criar, Excluir, Detalhes, Editar e Índice :
Views/Movies/*.cshtml

Uma classe de contexto de banco de dados: Data/MvcMovieContext.cs

A criação automática desses arquivos e atualizações de arquivo é conhecida como


scaffolding.

As páginas com scaffolded ainda não podem ser usadas porque o banco de dados não
existe. Executar o aplicativo e selecionar o link aplicativo de filme resulta em um Não é
possível abrir o banco de dados ou nenhuma tabela desse tipo: Mensagem de erro de
filme.
Compile o aplicativo para verificar se não há erros.

Migração inicial
Use o EF Core recurso Migrações para criar o banco de dados. As migrações são um
conjunto de ferramentas que criam e atualizam um banco de dados para corresponder
ao modelo de dados.

Visual Studio

No menu Ferramentas, selecione NuGet Package ManagerPackage Manager>


Console .

No PMC (Console do Gerenciador de Pacotes), Insira os seguintes comandos:

PowerShell

Add-Migration InitialCreate
Update-Database

Add-Migration InitialCreate : gera um arquivo de

Migrations/{timestamp}_InitialCreate.cs migração. O argumento


InitialCreate é o nome da migração. Qualquer nome pode ser usado, mas,

por convenção, um nome que descreve a migração é selecionado. Como essa


é a primeira migração, a classe gerada contém o código para criar o esquema
de banco de dados. O esquema de banco de dados é baseado no modelo
especificado na classe MvcMovieContext .

Update-Database : Atualizações o banco de dados para a migração mais

recente, que o comando anterior criou. Esse comando executa o Up método


no Migrations/{time-stamp}_InitialCreate.cs arquivo , que cria o banco de
dados.

O Update-Database comando gera 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()'.
Ignore o aviso anterior, corrigido em um tutorial posterior.

Para obter mais informações sobre as ferramentas pmc para EF Core, consulte EF
Core referência de ferramentas – PMC no Visual Studio.

Testar o aplicativo
Execute o aplicativo e selecione o link Aplicativo de Filme .

Se você receber uma exceção semelhante à seguinte, talvez tenha perdido o dotnet ef
database update comando na etapa de migrações:

Visual Studio

Console

SqlException: Cannot open database "MvcMovieContext-1" requested by the


login. The login failed.

7 Observação

Talvez você não consiga inserir casas decimais ou vírgulas no campo Price . Para
dar suporte à validação do jQuery para localidades 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 .

Examinar a classe de contexto e o registro de banco de


dados gerados
Com EF Coreo , o acesso a dados é executado usando um modelo. Um modelo é feito
de classes de entidade e um objeto de contexto que representa uma sessão com o
banco de dados. O objeto de contexto permite consultar e salvar dados. O contexto de
banco de dados é derivado de Microsoft. EntityFrameworkCore.DbContext e especifica
as entidades a serem incluídas no modelo de dados.

O scaffolding cria a classe de contexto do Data/MvcMovieContext.cs banco de dados:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;

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

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


}
}

O código anterior cria uma propriedade DbSet<Movie> que representa os filmes no


banco de dados.

Injeção de dependência
O ASP.NET Core foi criado com a DI (injeção de dependência). Serviços, como o
contexto do banco de dados, são registrados com DI no Program.cs . Esses serviços são
fornecidos a componentes que os exigem por meio de parâmetros de construtor.

Controllers/MoviesController.cs No arquivo , o construtor usa Injeção de Dependência

para injetar o contexto do MvcMovieContext banco de dados no controlador. O contexto


de banco de dados é usado em cada um dos métodos CRUD no controlador.

O scaffolding gerou o seguinte código realçado em Program.cs :

Visual Studio

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
O sistema de configuração ASP.NET Core lê a cadeia de conexão de banco de dados
"MvcMovieContext".

Examinar a cadeia de conexão de banco de dados gerada


O scaffolding adicionou uma cadeia de conexão ao appsettings.json arquivo:

Visual Studio

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=MvcMovieContext-
7dc5;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Para desenvolvimento local, o sistema de configuração ASP.NET Core lê a


ConnectionString chave do appsettings.json arquivo.

A classe InitialCreate
Examine o Migrations/{timestamp}_InitialCreate.cs arquivo de migração:

C#

using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace MvcMovie.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)",
nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2",
nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)",
nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)",
nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}

protected override void Down(MigrationBuilder migrationBuilder)


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

No código anterior:

InitialCreate.Up cria a tabela Movie e configura Id como a chave primária.

InitialCreate.Down reverte as alterações de esquema feitas pela Up migração.

Injeção de dependência no controlador


Abra o Controllers/MoviesController.cs arquivo e examine o construtor:

C#

public class MoviesController : Controller


{
private readonly MvcMovieContext _context;

public MoviesController(MvcMovieContext context)


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

Teste a página Criar. Inserir e enviar dados.

Teste os links Editar, Detalhes e Excluir.

Modelos fortemente tipado e a @model diretiva


Anteriormente neste tutorial, você viu como um controlador pode passar dados ou
objetos para uma exibição usando o dicionário ViewData . O dicionário ViewData é um
objeto dinâmico que fornece uma maneira conveniente de associação tardia para passar
informações para uma exibição.

O MVC fornece a capacidade de passar objetos de modelo fortemente tipado para uma
exibição. Essa abordagem fortemente tipada permite a verificação de código em tempo
de compilação. O mecanismo de scaffolding passou um modelo fortemente tipado na
classe e nas MoviesController exibições.

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

C#

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

var movie = await _context.Movie


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

return View(movie);
}

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


https://localhost:5001/movies/details/1 define:

O controlador para o movies controlador, o primeiro segmento de URL.


A ação para details , o segundo segmento de URL.
O id para 1, o último segmento de URL.

O id pode ser passado com uma cadeia de caracteres de consulta, como no exemplo a
seguir:

https://localhost:5001/movies/details?id=1

O id parâmetro é definido como um tipo anulável ( int? ) nos casos em que o id valor
não é fornecido.

Uma expressão lambda é passada para o FirstOrDefaultAsync método para selecionar


entidades de filme que correspondem aos dados de rota ou ao valor da cadeia de
caracteres de consulta.

C#

var movie = await _context.Movie


.FirstOrDefaultAsync(m => m.Id == id);

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

C#

return View(movie);

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

CSHTML

@model MvcMovie.Models.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>

A instrução @model na parte superior do arquivo de exibição especifica o tipo de objeto


que a exibição espera. Quando o controlador de filme foi criado, a seguinte instrução
@model foi incluída:

CSHTML

@model MvcMovie.Models.Movie

Essa diretiva @model permite o acesso ao filme que o controlador passou para a
exibição. O objeto Model é fortemente tipado. Por exemplo, no modo de exibição
Details.cshtml , o código passa cada campo de filme para os DisplayNameFor Auxiliares
html e DisplayFor com o objeto fortemente tipado Model . 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 Index método 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:

C#
// GET: Movies
public async Task<IActionResult> Index(string searchString)
{
return _context.Movie != null ?
View(await _context.Movie.ToListAsync()) :
Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

O código retornará detalhes do problema se a Movie propriedade do contexto de dados


for nula.

Quando o controlador de filmes foi criado, o scaffolding incluiu a seguinte @model


instrução na parte superior do Index.cshtml arquivo:

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

A @model diretiva permite acesso à lista de filmes que o controlador passou para a
exibição usando um Model objeto fortemente tipado. Por exemplo, no modo de
exibição Index.cshtml , o código percorre os filmes com uma foreach instrução sobre o
objeto fortemente tipado Model :

CSHTML

@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 Model objeto é fortemente tipado como um IEnumerable<Movie> objeto , cada


item no loop é digitado como Movie . Entre outros benefícios, o compilador valida os
tipos usados no código.

Recursos adicionais
Entity Framework Core para iniciantes
Auxiliares de Marcas
Globalização e localização

Anterior: adicionando uma exibição a seguir: trabalhando com SQL


Parte 5, trabalhe com um banco de
dados em um aplicativo MVC ASP.NET
Core
Artigo • 02/12/2022 • 15 minutos para o fim da leitura

Por Rick Anderson e Jon P Smith .

O objeto MvcMovieContext cuida da tarefa de se conectar ao banco de dados e mapear


objetos Movie para registros do banco de dados. O contexto do banco de dados é
registrado com o contêiner injeção de dependência no Program.cs arquivo:

Visual Studio

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

O sistema de configuração de ASP.NET Core lê a ConnectionString chave. Para


desenvolvimento local, ele obtém a cadeia de conexão do appsettings.json
arquivo:

JSON

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

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 SQL
Server de produção. Para obter mais informações, confira Configuração.

Visual Studio
SQL Server Express LocalDB
LocalDB:

É uma versão leve do Mecanismo de Banco de Dados SQL Server Express,


instalada por padrão com o Visual Studio.
Inicia sob demanda usando uma cadeia de conexão.
É direcionado para o desenvolvimento de programas. Ele é executado no
modo de usuário, portanto, não há configuração complexa.
Por padrão, cria arquivos .mdf no diretório C:/Users/{user} .

Propagar o banco de dados


Crie uma nova classe chamada SeedData na pasta Models. Substitua o código gerado
pelo seguinte:

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MvcMovie.Data;
using System;
using System.Linq;

namespace MvcMovie.Models;

public static class SeedData


{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-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 filmes no banco de dados, o inicializador de semente retornará e nenhum


filme será adicionado.

C#

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

Adicionar o inicializador de semeadura

Visual Studio

Substitua o conteúdo de Program.cs pelo seguinte código. O novo código está


realçado.

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MvcMovie.Data;
using MvcMovie.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

// Add services to the container.


builder.Services.AddControllersWithViews();

var app = builder.Build();

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


{
var services = scope.ServiceProvider;

SeedData.Initialize(services);
}

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Exclua todos os registros no banco de dados. Faça isso com os links Excluir no
navegador ou no SSOX.

Testar o aplicativo. Force o aplicativo a inicializar, chamando o código no


Program.cs arquivo para que o método de semente seja executado. Para forçar a

inicialização, feche a janela do prompt de comando que o Visual Studio abriu e


reinicie pressionando Ctrl+F5.
O aplicativo mostra os dados propagados.

Anterior: adicionando um modelo

Next: adicionando métodos e exibições do controlador


Parte 6, métodos de controlador e
exibições no ASP.NET Core
Artigo • 02/12/2022 • 27 minutos para o fim da leitura

De Rick Anderson

Temos um bom começo para o aplicativo de filme, mas a apresentação não é ideal. Por
exemplo, ReleaseDate deveria ser separado em duas palavras.

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

C#

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

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}

DataAnnotations são explicados 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 de Âncora MVC
Principal no Views/Movies/Index.cshtml arquivo.
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 HTML href do método de

ação do controlador e da ID de rota. Use Exibir Fonte do navegador favorito ou use as


ferramentas de desenvolvedor para examinar a marcação gerada. Uma parte do HTML
gerado é mostrada abaixo:

HTML

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

Lembre-se do formato para o conjunto de roteamento no Program.cs arquivo:

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

O ASP.NET Core converte https://localhost:5001/Movies/Edit/4 de uma solicitação no


método de ação Edit do controlador Movies com o parâmetro Id igual a 4. (Os
métodos do controlador também são conhecidos como métodos de ação.)

Os Auxiliares de Marcação são um dos novos recursos mais populares do ASP.NET Core.
Para obter mais informações, consulte Recursos adicionais.

Abra o controlador Movies e examine os dois métodos de ação Edit . O código a seguir
mostra o HTTP GET Edit método , que busca o filme e preenche o formulário de edição
gerado pelo Edit.cshtml Razor arquivo.

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

var movie = await _context.Movie.FindAsync(id);


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

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

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, 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,Rating")] 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);
}

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

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, 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,Rating")] 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);
}

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 ValidateAntiForgeryToken atributo é usado para impedir a falsificação de uma


solicitação e é emparelhado com um token anti-falsificaçã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.

CSHTML

<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 Impedir ataques de

XSRF/CSRF (solicitação intersite forjada) no ASP.NET Core.

O método HttpGet Edit usa o parâmetro ID de filme, pesquisa o filme usando o


método FindAsync 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.

C#

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

var movie = await _context.Movie.FindAsync(id);


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

Quando o sistema de scaffolding criou a exibição de Edição, ele examinou a classe


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

CSHTML

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

HTML

<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="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmq
UyXnJBXhmrjcUVDJyDUMm7-
MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

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

Processando a solicitação POST


A lista a seguir mostra a versão [HttpPost] do método de ação Edit .

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, 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,Rating")] 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);
}

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 . A ModelState.IsValid propriedade
verifica se os dados enviados no formulário podem ser usados para modificar (editar ou
atualizar) um Movie objeto. 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 de Validação no Views/Movies/Edit.cshtml
modelo de exibição cuida da exibição de 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 HTTP GET método também viola as práticas recomendadas de HTTP e o padrão de
arquitetura REST , 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
Impedir ataques XSRF/CSRF (solicitação entre sites) em ASP.NET Core
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
Auxiliar de Marcação de seleção
Auxiliar de marcação de validação

Anterior Avançar
Parte 7, adicionar pesquisa a um
aplicativo MVC ASP.NET Core
Artigo • 02/12/2022 • 23 minutos para o fim da leitura

De Rick Anderson

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

Atualize o Index método encontrado dentro Controllers/MoviesController.cs com o


seguinte código:

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

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

return View(await movies.ToListAsync());


}

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

C#

var movies = from m in _context.Movie


select m;

A consulta só é 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:
C#

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

O código s => s.Title!.Contains(searchString) acima é uma Expressão Lambda.


Lambdas são usados em consultas LINQ baseadas em método como argumentos para
métodos de operador de consulta padrão, como o Where método 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 Contains método é 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 até /Movies/Index . Acrescente uma cadeia de consulta, como ?


searchString=Ghost , à URL. Os filmes filtrados são exibidos.
Se você alterar a assinatura do Index método para ter um parâmetro chamado id , o id
parâmetro corresponderá ao espaço reservado opcional {id} para as rotas padrão
definidas em Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

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

O método Index anterior:

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

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

return View(await movies.ToListAsync());


}

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

C#

public async Task<IActionResult> Index(string id)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

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

return View(await movies.ToListAsync());


}

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

No entanto, você não pode esperar que os usuários modifiquem a URL sempre que
desejarem pesquisar um filme. 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 :

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


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

return View(await movies.ToListAsync());


}

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


abaixo:

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

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

<h2>Index</h2>

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

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


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

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.

C#

[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:
{PORT}/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. Corrija isso especificando que a solicitação deve ser
HTTP GET encontrada no Views/Movies/Index.cshtml arquivo .

CSHTML

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

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

CSHTML

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

Adicionar pesquisa por gênero


Adicione a seguinte classe MovieGenreViewModel à pasta Models:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models;

public class MovieGenreViewModel


{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}

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

Uma lista de filmes.


Uma SelectList que contém a lista de gêneros. Isso permite que o usuário
selecione um gênero na lista.
MovieGenre , que contém o gênero selecionado.
SearchString , que contém o texto que os usuários inserem na caixa de texto de

pesquisa.

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

C#

// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string
searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;

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

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

var movieGenreVM = new MovieGenreViewModel


{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};

return View(movieGenreVM);
}

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

C#

// Use LINQ to get list of genres.


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

A SelectList de gêneros é criada com a projeção dos gêneros distintos (não desejamos
que nossa lista de seleção tenha gêneros duplicados).

Quando o usuário pesquisa o item, o valor de pesquisa é mantido na caixa de pesquisa.

Adicionar pesquisa por gênero à exibição


Índice
Atualize Index.cshtml , encontrado em Views/Movies/, da seguinte maneira:

CSHTML
@model MvcMovie.Models.MovieGenreViewModel

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

<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.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 auxiliar HTML a seguir:

@Html.DisplayNameFor(model => model.Movies![0].Title)

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


referenciada na expressão lambda para determinar o nome de exibição. Como a
expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de
acesso quando model , model.Movies ou model.Movies[0] é null ou vazio. Quando a
expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem =>
item.Title) ), os valores da propriedade do modelo são avaliados. O ! após

model.Movies é o operador tolerante a nulo, que é usado para declarar que Movies não
é nulo.

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


Anterior Avançar
Parte 8, adicionar um novo campo a um
aplicativo MVC ASP.NET Core
Artigo • 02/12/2022 • 21 minutos para o fim da leitura

De Rick Anderson

Nesta seção, as Migrações do Entity Framework Code First são usadas para:

Adicionar um novo campo ao modelo.


Migrar o novo campo para o banco de dados.

Ao usar o Code First do EF para criar automaticamente um banco de dados, o Code


First:

Adiciona uma tabela ao banco de dados para acompanhar o esquema do banco


de dados.
Verifica se o banco de dados está em sincronia com as classes de modelo das
quais foi gerado. Se ele não estiver sincronizado, o EF gerará uma exceção. Isso
facilita a descoberta de problemas de código/banco de dados inconsistente.

Adicionar uma Propriedade de Classificação ao


Modelo de Filme
Adicione uma Rating propriedade a Models/Movie.cs :

C#

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

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }

[Display(Name = "Release Date")]


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

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
public string? Rating { get; set; }
}

Compilar o aplicativo

Visual Studio

Ctrl+Shift+B

Como você adicionou um novo campo à Movie classe , é necessário atualizar a lista de
associação de propriedades para que essa nova propriedade seja incluída. No
MoviesController.cs , atualize o [Bind] atributo para os Create métodos de ação e

Edit para incluir a Rating propriedade :

C#

[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 /Views/Movies/Index.cshtml arquivo e adicione um Rating campo:

CSHTML

<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<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>

Atualize o /Views/Movies/Create.cshtml com um Rating campo .

Visual Studio/Visual Studio para Mac

Copie/cole o “grupo de formulário” anterior e permita que o IntelliSense ajude você


a atualizar os campos. O IntelliSense funciona com os Auxiliares de Marcação.
Atualize os modelos restantes.

Atualize a classe SeedData para que ela forneça um valor para a nova coluna. Uma
alteração de amostra é mostrada abaixo, mas é recomendável fazer essa alteração em
cada new Movie .

C#

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

No menu Ferramentas , selecione Console do Gerenciador de Pacotes do


Gerenciador de Pacotes > NuGet.

No PMC, insira os seguintes comandos:


PowerShell

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 e exibir filmes com um Rating
campo.

Anterior Avançar
Parte 9, adicionar validação a um
aplicativo MVC ASP.NET Core
Artigo • 03/12/2022 • 30 minutos para o fim da leitura

De Rick Anderson

Nesta seção:

A lógica de validação é adicionada ao modelo Movie .


Você garante que as regras de validação sejam impostas sempre que um usuário
criar ou editar um filme.

Mantendo o processo DRY


Um dos princípios de design do MVC é o DRY (“Don't Repeat Yourself”). O ASP.NET
Core MVC incentiva você a especificar a funcionalidade ou o comportamento somente
uma vez e, em seguida, refleti-lo em qualquer lugar de um aplicativo. Isso reduz a
quantidade de código que você precisa escrever e faz com que o código escrito seja
menos propenso a erros e mais fácil de testar e manter.

O suporte de validação fornecido pelo MVC e pelo Entity Framework Core Code First é
um bom exemplo do princípio DRY em ação. Especifique as regras de validação de
forma declarativa em um único lugar (na classe de modelo) e as regras são impostas em
qualquer lugar no aplicativo.

Adicionar regras de validação ao modelo de


filme
O namespace DataAnnotations fornece um conjunto de atributos de validação internos
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 internos.

C#

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

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


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

[Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
[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 anterior, "Gênero":
Deve usar apenas letras.
A primeira letra deve ser maiúscula. Espaços em branco são permitidos
enquanto números e caracteres especiais não são permitidos.

A "Classificação" RegularExpression :
Exige que o primeiro caractere seja uma letra maiúscula.
Permite caracteres especiais e números nos espaços subsequentes. "PG-13" é
válido para uma classificação, mas é um erro em "Gênero".
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.

Interface do usuário de erro de validação


Execute o aplicativo e navegue para o controlador Movies.

Selecione o link Criar Novo para adicionar um novo filme. Preencha o formulário com
alguns valores inválidos. Assim que a validação do lado do cliente do jQuery detecta o
erro, ela exibe uma mensagem de erro.
7 Observação

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. Consulte
este comentário do GitHub 4076 para obter instruções sobre como adicionar
vírgula decimal.
Observe como o formulário renderizou automaticamente uma mensagem de erro de
validação apropriada em cada campo que contém um valor inválido. Os erros são
impostos no lado do cliente (usando o JavaScript e o jQuery) e no lado do servidor (caso
um usuário tenha o JavaScript desabilitado).

Um benefício significativo é que você não precisava alterar uma única linha de código
na MoviesController classe ou no Create.cshtml modo de exibição para habilitar essa
interface do usuário de validação. O controlador e as exibições criados anteriormente
neste tutorial selecionaram automaticamente as regras de validação especificadas com
atributos de validação nas propriedades da classe de modelo Movie . Teste a validação
usando o método de ação Edit e a mesma validação é aplicada.

Os dados de formulário não serão enviados para o servidor enquanto houver erros de
validação do lado do cliente. Verifique isso colocando um ponto de interrupção no
método HTTP Post usando a ferramenta Fiddler ou as ferramentas do Desenvolvedor
F12.

Como funciona a validação


Talvez você esteja se perguntando como a interface do usuário de validação foi gerada
sem atualizações do código no controlador ou nas exibições. O código a seguir mostra
os dois métodos Create .

C#

// GET: Movies/Create
public IActionResult Create()
{
return View();
}

// POST: Movies/Create
// To protect from overposting attacks, 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>
Create([Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(movie);
}

O primeiro método de ação (HTTP GET) Create exibe o formulário Criar inicial. A
segunda versão ( [HttpPost] ) manipula a postagem de formulário. O segundo método
Create (a versão [HttpPost] ) chama ModelState.IsValid para verificar se o filme tem

erros de validação. A chamada a esse método avalia os atributos de validação que


foram aplicados ao objeto. Se o objeto tiver erros de validação, o método Create
exibirá o formulário novamente. Se não houver erros, o método salvará o novo filme no
banco de dados. Em nosso exemplo de filme, o formulário não é postado no servidor
quando há erros de validação detectados no lado do cliente; o segundo método Create
nunca é chamado quando há erros de validação do lado do cliente. Se você desabilitar o
JavaScript no navegador, a validação do cliente será desabilitada e você poderá testar o
método Create HTTP POST ModelState.IsValid detectando erros de validação.

Defina um ponto de interrupção no método [HttpPost] Create e verifique se o método


nunca é chamado; a validação do lado do cliente não enviará os dados de formulário
quando forem detectados erros de validação. Se você desabilitar o JavaScript no
navegador e, em seguida, enviar o formulário com erros, o ponto de interrupção será
atingido. Você ainda pode obter uma validação completa sem o JavaScript.

A imagem a seguir mostra como desabilitar o JavaScript no navegador Firefox.


A imagem a seguir mostra como desabilitar o JavaScript no navegador Chrome.
Depois de desabilitar o JavaScript, poste os dados inválidos e execute o depurador em
etapas.
Uma parte do Create.cshtml modelo de exibição é mostrada na seguinte marcação:

HTML

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

@*Markup removed for brevity.*@

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.

Usando atributos DataType


Abra o Movie.cs arquivo e examine a Movie classe . O namespace
System.ComponentModel.DataAnnotations fornece atributos de formatação, além do

conjunto interno de atributos de validação. Já aplicamos um valor de enumeração


DataType à data de lançamento e aos campos de preço. O código a seguir mostra as
propriedades ReleaseDate e Price com o atributo DataType apropriado.

C#

[Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

Os DataType atributos fornecem apenas dicas para o mecanismo de exibição formatar


os dados e fornece elementos/atributos, como <a> para URLs e para email <a
href="mailto:EmailAddress.com"> . 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:

C#

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


true)]
public DateTime ReleaseDate { get; set; }

A configuração ApplyFormatInEditMode especifica que a formatação também deve ser


aplicada quando o valor é exibido em uma caixa de texto para edição. (Talvez você não
deseje isso em alguns campos – por exemplo, para valores de moeda, provavelmente,
você não deseja exibir o símbolo de moeda na caixa de texto para edição.)

Você pode usar o atributo DisplayFormat por si só, mas geralmente é uma boa ideia
usar o atributo DataType . O atributo DataType transmite a semântica dos dados, ao
invés de apresentar como renderizá-lo em uma tela e oferece os seguintes benefícios
que você não obtém com DisplayFormat:

O navegador pode habilitar os recursos do HTML5 (por exemplo, mostrar um


controle de calendário, o símbolo de moeda apropriado à localidade, links de
email, etc.)

Por padrão, o navegador renderizará os dados usando o formato correto de


acordo com a localidade.

O atributo DataType pode permitir que o MVC escolha o modelo de campo


correto para renderizar os dados (o DisplayFormat , se usado por si só, usa o
modelo de cadeia de caracteres).

7 Observação

A validação do jQuery não funciona com os atributos Range e DateTime . Por


exemplo, o seguinte código sempre exibirá um erro de validação do lado do
cliente, mesmo quando a data estiver no intervalo especificado:

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


Você precisará desabilitar a validação de data do jQuery para usar o atributo Range com
DateTime . Geralmente, não é uma boa prática compilar datas rígidas nos modelos e,
portanto, o uso do atributo Range e de DateTime não é recomendado.

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

C#

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

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$"), Required, StringLength(30)]
public string Genre { get; set; }
[Range(1, 100), DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[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
Trabalhar com formulários
Globalização e localização
Introdução aos auxiliares de marcação
Auxiliares de marca de autor

Anterior Avançar
Parte 10, examine os métodos Detalhes
e Excluir de um aplicativo ASP.NET Core
Artigo • 02/12/2022 • 9 minutos para o fim da leitura

De Rick Anderson

Abra o controlador Movie e examine o método Details :

C#

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

var movie = await _context.Movie


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

return View(movie);
}

O mecanismo de scaffolding MVC que criou este método de ação adiciona um


comentário mostrando uma solicitação HTTP que invoca o método. Nesse caso, é uma
solicitação GET com três segmentos de URL, o controlador Movies , o método Details e
um valor id . Lembre-se de que esses segmentos são definidos em Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

O EF facilita a pesquisa de dados usando o método FirstOrDefaultAsync . Um recurso


de segurança importante interno do método é que o código verifica se o método de
pesquisa encontrou um filme antes de tentar fazer algo com ele. Por exemplo, um
hacker pode introduzir erros no site alterando a URL criada pelos links de
http://localhost:{PORT}/Movies/Details/1 para algo como http://localhost:
{PORT}/Movies/Details/12345 (ou algum outro valor que não representa um filme real).

Se você não marcou um filme nulo, o aplicativo gerará uma exceção.

Examine os métodos Delete e DeleteConfirmed .

C#

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

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

var movie = await _context.Movie


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

return View(movie);
}

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

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:

C#

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

C#

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{

O CLR (Common Language Runtime) exige que os métodos sobrecarregados tenham


uma assinatura de parâmetro exclusiva (mesmo nome de método, mas uma lista
diferente de parâmetros). No entanto, aqui você precisa de dois métodos Delete – um
para GET e outro para POST – que têm a mesma assinatura de parâmetro. (Ambos
precisam aceitar um único inteiro como parâmetro.)

Há duas abordagens para esse problema e uma delas é fornecer aos métodos nomes
diferentes. Foi isso o que o mecanismo de scaffolding fez no exemplo anterior. No
entanto, isso apresenta um pequeno problema: o ASP.NET mapeia os segmentos de
URL para os métodos de ação por nome e se você renomear um método, o roteamento
normalmente não conseguirá encontrar esse método. A solução é o que você vê no
exemplo, que é adicionar o atributo ActionName("Delete") ao método DeleteConfirmed .
Esse atributo executa o mapeamento para o sistema de roteamento, de modo que uma
URL que inclui /Delete/ para uma solicitação POST encontre o método DeleteConfirmed .

Outra solução alternativa comum para métodos que têm nomes e assinaturas idênticos
é alterar artificialmente a assinatura do método POST para incluir um parâmetro extra
(não utilizado). Foi isso o que fizemos em uma postagem anterior quando adicionamos
o parâmetro notUsed . Você pode fazer o mesmo aqui para o método [HttpPost]
Delete :

C#

// POST: Movies/Delete/6
[HttpPost]
[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 ASP.NET Core e Banco de Dados SQL no Serviço de Aplicativo do Azure.

Anterior
Tutoriais de Blazor do ASP.NET Core
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Os seguintes tutoriais estão disponíveis sobre o Blazor no ASP.NET Core:

Criar seu primeiro aplicativo Blazor (Blazor Server)

Criar um aplicativo de lista de tarefas pendentes Blazor (Blazor Server ou Blazor


WebAssembly)

Usar ASP.NET Core SignalR com Blazor (Blazor Server ou Blazor WebAssembly)

Tutoriais do ASP.NET Core Blazor Hybrid

Módulos de aprendizagem

Para obter mais informações sobre os modelos de hospedagem Blazor, Blazor Server e
Blazor WebAssembly, consulte Modelos de hospedagem de Blazor do ASP.NET Core.
Tutorial: criar uma API Web com o
ASP.NET Core
Artigo • 10/01/2023 • 69 minutos para o fim da leitura

Por Rick Anderson e Kirk Larkin

Este tutorial ensina os conceitos básicos da criação de uma API Web usando um banco
de dados.

Visão geral
Este tutorial cria a seguinte API:

API Descrição Corpo da Corpo da resposta


solicitação

GET /api/todoitems Obter todos os itens de Nenhum Matriz de itens de


tarefas pendentes tarefas pendentes

GET Obter um item por ID Nenhum Item de tarefas


/api/todoitems/{id} pendentes

POST Adicionar um novo item Item de tarefas Item de tarefas


/api/todoitems pendentes pendentes

PUT Atualizar um item existente Item de tarefas Nenhum


/api/todoitems/{id} pendentes

DELETE Excluir um item Nenhum Nenhum


/api/todoitems/{id}

O diagrama a seguir mostra o design do aplicativo.


Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um projeto Web


Visual Studio
No menu Arquivo, selecione Novo>Projeto.
Insira API Web na caixa de pesquisa.
Selecione o modelo de API Web ASP.NET Core e selecione Avançar.
Na caixa de diálogo Configurar seu novo projeto, nomeie o projeto TodoApi e
selecione Avançar.
Na caixa de diálogo Informações adicionais:
Confirme se o Framework é .NET 7.0 (ou posterior).
Confirme se a caixa de seleção para Usar controladores (desmarcar para
usar APIs mínimas) está marcada.
Selecione Criar.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Testar o projeto
O modelo de projeto cria uma WeatherForecast API com suporte para Swagger.

Visual Studio

Pressione Ctrl + F5 para execução sem o depurador.

O Visual Studio exibe a seguinte caixa de diálogo quando um projeto ainda não
está configurado para usar o SSL:

Selecione Sim se você confia no certificado SSL do IIS Express.


A seguinte caixa de diálogo é exibida:

Selecione Sim se você concordar com confiar no certificado de desenvolvimento.

Para obter informações sobre como confiar no navegador Firefox, consulte Firefox
SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

O Visual Studio inicia o navegador padrão e navega para https://localhost:


<port>/swagger/index.html , onde <port> é um número de porta escolhido
aleatoriamente.

A página /swagger/index.html swagger é exibida. Selecione


OBTER>Experimente>Executar. A página exibe:

O comando Curl para testar a API WeatherForecast.


A URL para testar a API WeatherForecast.
O código de resposta, o corpo e os cabeçalhos.
Uma caixa de listagem suspensa com tipos de mídia e o valor e o esquema de
exemplo.

Se a página do Swagger não aparecer, consulte este problema do GitHub .

O Swagger é usado para gerar páginas úteis de documentação e ajuda para APIs Web.
Este tutorial se concentra na criação de uma API Web. Para obter mais informações
sobre o Swagger, consulte ASP.NET Core documentação da API Web com
Swagger/OpenAPI.
Copie e cole a URL de Solicitação no navegador: https://localhost:
<port>/weatherforecast

JSON semelhante ao exemplo a seguir é retornado:

JSON

[
{
"date": "2019-07-16T19:04:05.7257911-06:00",
"temperatureC": 52,
"temperatureF": 125,
"summary": "Mild"
},
{
"date": "2019-07-17T19:04:05.7258461-06:00",
"temperatureC": 36,
"temperatureF": 96,
"summary": "Warm"
},
{
"date": "2019-07-18T19:04:05.7258467-06:00",
"temperatureC": 39,
"temperatureF": 102,
"summary": "Cool"
},
{
"date": "2019-07-19T19:04:05.7258471-06:00",
"temperatureC": 10,
"temperatureF": 49,
"summary": "Bracing"
},
{
"date": "2019-07-20T19:04:05.7258474-06:00",
"temperatureC": -1,
"temperatureF": 31,
"summary": "Chilly"
}
]

Adicionar uma classe de modelo


Um modelo é um conjunto de classes que representam os dados gerenciados pelo
aplicativo. O modelo para este aplicativo é a TodoItem classe .

Visual Studio
Em Gerenciador de Soluções, clique com o botão direito do mouse no
projeto. Selecione Adicionar>Nova Pasta. Nomeie a pasta Models .
Clique com o botão direito do mouse na Models pasta e selecione
Adicionar>Classe. Dê à classe o nome TodoItem e selecione Adicionar.
Substitua o código do modelo pelo seguinte:

C#

namespace TodoApi.Models;

public class TodoItem


{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

A propriedade Id funciona como a chave exclusiva em um banco de dados relacional.

As classes de modelo podem ir para qualquer lugar no projeto, mas a Models pasta é
usada por convenção.

Adicionar um contexto de banco de dados


O contexto de banco de dados é a classe principal que coordena a funcionalidade do
Entity Framework para um modelo de dados. Essa classe é criada derivando-a da classe
Microsoft.EntityFrameworkCore.DbContext.

Visual Studio

Adicionar pacotes NuGet


No menu Ferramentas , selecione Gerenciador de Pacotes > NuGet Gerenciar
Pacotes NuGet para solução.
Selecione a guia Procurar e, em seguida, insira
Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa.

Selecione Microsoft.EntityFrameworkCore.InMemory no painel esquerdo.


Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
Adicione o contexto de banco de dados
TodoContext
Clique com o botão direito do mouse na Models pasta e selecione
Adicionar>Classe. Nomeie a classe como TodoContext e clique em Adicionar.

Insira o seguinte código:

C#

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models;

public class TodoContext : DbContext


{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; } = null!;


}

Registrar o contexto do banco de dados


No ASP.NET Core, serviços como o contexto de BD precisam ser registrados no
contêiner de DI (injeção de dependência). O contêiner fornece o serviço aos
controladores.

Atualize Program.cs com o seguinte código realçado:

C#

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();


if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

O código anterior:

Adiciona using diretivas.


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.

Faça scaffold de um controlador


Visual Studio

Clique com o botão direito do mouse na pasta Controllers.

Selecione Adicionar>Novo Item Scaffolded.

Selecione Controlador de API com ações, usando o Entity Framework e, em


seguida, selecione Adicionar.

Na caixa de diálogo Adicionar Controlador de API com ações, usando o


Entity Framework:
Selecione TodoItem (TodoApi.Models) na classe Modelo.
Selecione TodoContext (TodoApi.Models) na classe de contexto Dados.
Selecione Adicionar.

Se a operação de scaffolding falhar, selecione Adicionar para tentar realizar


scaffolding uma segunda vez.

O código gerado:
Marca a classe com o [ApiController] atributo . Esse atributo indica se o
controlador responde às solicitações da API Web. Para obter informações sobre
comportamentos específicos habilitados pelo atributo, consulte Criar APIs Web
com ASP.NET Core.
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.

Os modelos de ASP.NET Core para:

Controladores com exibições incluem [action] no modelo de rota.


Os controladores de API não incluem [action] no modelo de rota.

Quando o [action] token não está no modelo de rota, o nome da ação (nome do
método) não é incluído no ponto de extremidade. Ou seja, o nome do método
associado da ação não é usado na rota correspondente.

Atualizar o método de criação PostTodoItem


Atualize a instrução return no PostTodoItem para usar o operador nameof :

C#

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

// return CreatedAtAction("GetTodoItem", new { id = todoItem.Id },


todoItem);
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id },
todoItem);
}

O código anterior é um HTTP POST método, conforme indicado pelo [HttpPost] atributo .
O método obtém o valor do TodoItem do corpo da solicitação HTTP.

Para obter mais informações, confira Roteamento de atributo com atributos Http[Verb].

O método CreatedAtAction:

Retornará um código de status HTTP 201 se tiver êxito. HTTP 201 é a resposta
padrão para um HTTP POST método que cria um novo recurso no servidor.
Adiciona um cabeçalho de Local à 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 .

Testar PostTodoItem
Pressione CTRL+F5 para executar o aplicativo.

Na janela do navegador Swagger, selecione POST /api/TodoItems e, em seguida,


selecione Experimentar.

Na janela Entrada do corpo da solicitação, atualize o JSativado. Por exemplo,

JSON

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

Selecione Executar
Testar o URI do cabeçalho de local
No POST anterior, a interface do usuário do Swagger mostra o cabeçalho de
localização em Cabeçalhos de resposta. Por exemplo, location:
https://localhost:7260/api/TodoItems/1 . O cabeçalho de local mostra o URI para o

recurso criado.

Para testar o cabeçalho de local:


Na janela do navegador Swagger, selecione GET /api/TodoItems/{id}e selecione
Experimentar.

Insira 1 na id caixa de entrada e selecione Executar.


Examine os métodos GET
Dois pontos de extremidade GET são implementados:

GET /api/todoitems
GET /api/todoitems/{id}

A seção anterior mostrou um exemplo da /api/todoitems/{id} rota.

Siga as instruções POST para adicionar outro item de tarefas pendentes e, em seguida,
teste a /api/todoitems rota usando o Swagger.

Este aplicativo usa um banco de dados em memória. Se o aplicativo for interrompido e


iniciado, a solicitação GET anterior não retornará nenhum dado. Se nenhum dado for
retornado, execute POST de dados no aplicativo.

Roteamento e caminhos de URL


O [HttpGet] atributo indica um método que responde a uma solicitação HTTP GET . O
caminho da URL de cada método é construído da seguinte maneira:

Comece com a cadeia de caracteres de modelo no atributo Route do controlador:

C#

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase

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 é TodoItemsController. Portanto, o nome do
controlador é "TodoItems". 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 ao método em seu id parâmetro .

C#

[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 retornados
O tipo de retorno dos GetTodoItems métodos e GetTodoItem é o tipo ActionResult<T>.
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no
corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200
OK , supondo que não haja exceções 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 de status NotFound404 .
Caso contrário, o método retornará 200 com um JScorpo de resposta ON. item
Retornar resulta em uma HTTP 200 resposta.

O método PutTodoItem
Examine o método PutTodoItem :

C#

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}

_context.Entry(todoItem).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return NoContent();
}

PutTodoItem é semelhante a PostTodoItem , exceto que usa HTTP PUT . A resposta é 204

(sem conteúdo) . De acordo com a especificação HTTP, uma PUT solicitação exige que
o cliente envie toda a entidade atualizada, não apenas as alterações. Para dar suporte a
atualizações parciais, use HTTP PATCH.

Testar o método PutTodoItem


Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que
o aplicativo é iniciado. Deverá haver um item no banco de dados antes de você fazer
uma chamada PUT. Chame GET para garantir que haja um item no banco de dados
antes de fazer uma chamada PUT.

Usando a interface do usuário do Swagger, use o botão PUT para atualizar o que tem id
TodoItem = 1 e defina seu nome como "feed fish" . Observe que a resposta é HTTP 204

No Content .
O método DeleteTodoItem
Examine o método DeleteTodoItem :

C#

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

Testar o método DeleteTodoItem


Use a interface do usuário do Swagger para excluir o que tem id TodoItem = 1. Observe
que a resposta é HTTP 204 No Content .

Testar com http-repl, Postman ou curl


http-repl, Postman e curl geralmente são usados para testar as APIs. O Swagger usa
curl e mostra o curl comando enviado.

Para obter instruções sobre essas ferramentas, consulte os seguintes links:

Testar APIs com o Postman


Instalar e testar APIs com http-repl

Para obter mais informações sobre http-repl , consulte Testar APIs Web com o
HttpRepl.

Impedir o excesso de postagem


Atualmente, o aplicativo de exemplo expõe todo TodoItem o objeto. Os aplicativos de
produção normalmente limitam os dados que são inseridos e retornados usando um
subconjunto do modelo. Há várias razões por trás disso, e a segurança é uma das
principais. O subconjunto de um modelo geralmente é chamado de DTO (Objeto de
Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado
neste tutorial.

Um DTO pode ser usado para:

Impedir o excesso de postagem.


Oculte as propriedades que os clientes não devem exibir.
Omita algumas propriedades para reduzir o tamanho do conteúdo.
Nivele grafos de objeto que contêm objetos aninhados. Grafos de objeto
mesclados podem ser mais convenientes para clientes.

Para demonstrar a abordagem DTO, atualize a TodoItem classe para incluir um campo
secreto:

C#

namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
}

O campo secreto precisa estar oculto deste aplicativo, mas um aplicativo administrativo
pode optar por expô-lo.

Verifique se você pode postar e obter o campo de segredo.

Criar um modelo de DTO:

C#

namespace TodoApi.Models;

public class TodoItemDTO


{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

Atualize o TodoItemsController para usar TodoItemDTO :


C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;

public TodoItemsController(TodoContext context)


{
_context = context;
}

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
}

// GET: api/TodoItems/5
// <snippet_GetByID>
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

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

return ItemToDTO(todoItem);
}
// </snippet_GetByID>

// PUT: api/TodoItems/5
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Update>
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO
todoDTO)
{
if (id != todoDTO.Id)
{
return BadRequest();
}

var todoItem = await _context.TodoItems.FindAsync(id);


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

todoItem.Name = todoDTO.Name;
todoItem.IsComplete = todoDTO.IsComplete;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
{
return NotFound();
}

return NoContent();
}
// </snippet_Update>

// POST: api/TodoItems
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Create>
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO
todoDTO)
{
var todoItem = new TodoItem
{
IsComplete = todoDTO.IsComplete,
Name = todoDTO.Name
};

_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
}
// </snippet_Create>

// DELETE: api/TodoItems/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();
}

private bool TodoItemExists(long id)


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

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>


new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
}

Verifique se você não pode postar ou obter o campo de segredo.

Chamar a API Web com o JavaScript


Consulte Tutorial: Chamar uma API Web ASP.NET Core com JavaScript.

Série de vídeos da API Web


Confira Vídeo: Série do Iniciante para: APIs Web.

Adicionar suporte de autenticação a uma API


Web
Identity ASP.NET Core adiciona a funcionalidade de logon da interface do usuário para
ASP.NET Core aplicativos Web. Para proteger APIs Web e SPAs, use um dos seguintes:

Azure Active Directory


Azure Active Directory B2C (Azure AD B2C)
Servidor Duende Identity
O Duende Identity Server é uma estrutura do OpenID Connect e do OAuth 2.0 para
ASP.NET Core. O Servidor Duende Identity habilita os seguintes recursos de segurança:

AaaS (autenticação como serviço)


SSO (logon único) em vários tipos de aplicativo
Controle de acesso para APIs
Gateway de Federação

) Importante

O Software Duende pode exigir que você pague uma taxa de licença pelo uso de
produção do Duende Identity Server. Para obter mais informações, consulte Migrar
do ASP.NET Core 5.0 para o 6.0.

Para obter mais informações, consulte a documentação do Servidor Duende Identity


(site do Duende Software) .

Publicar no Azure
Para obter informações sobre como implantar no Azure, consulte Início Rápido:
Implantar um aplicativo Web ASP.NET.

Recursos adicionais
Exibir ou baixar o código de exemplo para este tutorial . Consulte como baixar.

Para saber mais, consulte os recursos a seguir:

Criar APIs Web com o ASP.NET Core


Tutorial: Criar uma API Web mínima com ASP.NET Core
Documentação da API Web ASP.NET Core com o Swagger/OpenAPI
RazorPáginas com o Entity Framework Core em ASP.NET Core – Tutorial 1 de 8
Roteamento para ações do controlador no ASP.NET Core
Tipos de retorno de ação do controlador na API Web do ASP.NET Core
Implantar aplicativos ASP.NET Core no Serviço de Aplicativo do Azure
Hospedar e implantar o ASP.NET Core
Criar uma API Web com o ASP.NET Core
Tutorial: Criar uma API Web mínima
com ASP.NET Core
Artigo • 10/01/2023 • 33 minutos para o fim da leitura

Por Rick Anderson e Tom Dykstra

APIs mínimas são arquitetas para criar APIs HTTP com dependências mínimas. Eles são
ideais para microsserviços e aplicativos que desejam incluir apenas os arquivos mínimos,
recursos e dependências em ASP.NET Core.

Este tutorial ensina os conceitos básicos da criação de uma API Web mínima com
ASP.NET Core. Para obter um tutorial sobre como criar um projeto de API Web com
base em controladores que contêm mais recursos, consulte Criar uma API Web. Para
obter uma comparação, consulte Diferenças entre APIs mínimas e APIs com
controladores mais adiante neste tutorial.

Visão geral
Este tutorial cria a seguinte API:

API Descrição Corpo da Corpo da resposta


solicitação

GET /todoitems Obter todos os itens de Nenhum Matriz de itens de


tarefas pendentes tarefas pendentes

GET Obter itens pendentes Nenhum Matriz de itens de


/todoitems/complete concluídos tarefas pendentes

GET Obter um item por ID Nenhum Item de tarefas


/todoitems/{id} pendentes

POST /todoitems Adicionar um novo item Item de tarefas Item de tarefas


pendentes pendentes

PUT Atualizar um item existente Item de tarefas Nenhum


/todoitems/{id} pendentes

DELETE Excluir um item Nenhum Nenhum


/todoitems/{id}

Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um projeto de API Web


Visual Studio

Inicie o Visual Studio 2022 e selecione Criar um novo projeto.

Na caixa de diálogo Criar um novo projeto :


Insira Empty na caixa de pesquisa Pesquisar modelos .
Selecione o modelo ASP.NET Core Vazio e selecione Avançar.
Nomeie o projeto ComoApi e selecione Avançar.

Na caixa de diálogo Informações adicionais:


Selecione .NET 7.0
Desmarcar Não usar instruções de nível superior
Escolha Criar
Examinar o código
O Program.cs arquivo contém o seguinte código:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

O código anterior:

Cria um WebApplicationBuilder e um WebApplication com padrões pré-


configurados.
Cria um ponto de extremidade / HTTP GET que retorna Hello World! :

Executar o aplicativo

Visual Studio

Pressione Ctrl + F5 para execução sem o depurador.

O Visual Studio exibe a caixa de diálogo a seguir:

Selecione Sim se você confia no certificado SSL do IIS Express.

A seguinte caixa de diálogo é exibida:


Selecione Sim se você concordar com confiar no certificado de desenvolvimento.

Para obter informações sobre como confiar no navegador Firefox, consulte Firefox
SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

O Visual Studio inicia o Kestrel servidor Web e abre uma janela do navegador.

Hello World! é exibido no navegador. O Program.cs arquivo contém um aplicativo

mínimo, mas completo.

Adicionar pacotes NuGet


Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e ao
diagnóstico usados neste tutorial.

Visual Studio

No menu Ferramentas , selecione Gerenciador de Pacotes > NuGet Gerenciar


Pacotes NuGet para solução.
Selecione a guia Procurar.
Insira Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa e
selecione Microsoft.EntityFrameworkCore.InMemory .
Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
Siga as instruções anteriores para adicionar o
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacote.

As classes de contexto de modelo e banco de


dados
Na pasta do projeto, crie um arquivo chamado Todo.cs com o seguinte código:

C#

class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

O código anterior cria o modelo para este aplicativo. Um modelo é uma classe que
representa os dados que o aplicativo gerencia.

Crie um arquivo chamado TodoDb.cs com o seguinte código:

C#

using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext


{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }

public DbSet<Todo> Todos => Set<Todo>();


}

O código anterior define o contexto do banco de dados, que é a classe principal que
coordena a funcionalidade do Entity Framework para um modelo de dados. Essa classe
deriva da Microsoft.EntityFrameworkCore.DbContext classe .

Adicionar o código de API


Substitua o conteúdo do arquivo Program.cs pelo seguinte código:

C#
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>


await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>


await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}

return Results.NotFound();
});
app.Run();

O código realçado a seguir adiciona o contexto do banco de dados ao contêiner de DI


(injeção de dependência) e permite exibir exceções relacionadas ao banco de dados:

C#

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

O contêiner de DI fornece acesso ao contexto do banco de dados e a outros serviços.

Instalar o Postman para testar o aplicativo


Este tutorial usa o Postman para testar a API.

Instalar o Postman
Inicie o aplicativo Web.
Inicie o Postman.
Desabilite a Verificação do certificado SSL
Para o Postman para Windows, selecioneConfigurações de Arquivo> (guia
Geral), desabilite a verificação do certificado SSL.
Para o Postman para macOS, selecionePreferências do Postman> (guia Geral),
desabilite a verificação do certificado SSL.

2 Aviso

Habilite novamente a verificação de certificado SSL depois de testar o


aplicativo de exemplo.

Dados de postagem de teste


O código a seguir em Program.cs cria um ponto /todoitems de extremidade HTTP POST
que adiciona dados ao banco de dados na memória:

C#

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

Execute o aplicativo. O navegador exibe um erro 404 porque não há mais um / ponto
de extremidade.

Use o ponto de extremidade POST para adicionar dados ao aplicativo:

Crie uma nova solicitação HTTP.

Defina o método HTTP como POST .

Defina o URI como https://localhost:<port>/todoitems . Por exemplo:


https://localhost:5001/todoitems

Selecione a guia Corpo.

Selecione bruto.

Defina o tipo como JSON.

No corpo da solicitação, insira JSON para um item pendente:

JSON

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

Selecione Enviar.
Examinar os pontos de extremidade GET
O aplicativo de exemplo implementa vários pontos de extremidade GET chamando
MapGet :

API Descrição Corpo da Corpo da resposta


solicitação

GET /todoitems Obter todos os itens de Nenhum Matriz de itens de tarefas


tarefas pendentes pendentes

GET Obter todos os itens Nenhum Matriz de itens de tarefas


/todoitems/complete pendentes concluídos pendentes

GET Obter um item por ID Nenhum Item de tarefas


/todoitems/{id} pendentes

C#

app.MapGet("/todoitems", async (TodoDb db) =>


await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

Testar os pontos de extremidade GET


Teste o aplicativo chamando os pontos de extremidade de um navegador ou postman.
As etapas a seguir são para o Postman.

Crie uma nova solicitação HTTP.


Defina o método HTTP como GET.
Defina o URI de solicitação como https://localhost:<port>/todoitems . Por
exemplo, https://localhost:5001/todoitems .
Selecione Enviar.

A chamada para GET /todoitems produz uma resposta semelhante à seguinte:

JSON

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

Defina o URI de solicitação como https://localhost:<port>/todoitems/1 . Por


exemplo, https://localhost:5001/todoitems/1 .
Selecione Enviar.
A resposta é semelhante ao seguinte:

JSON

{
"id": 1,
"name": "walk dog",
"isComplete": false
}
Este aplicativo usa um banco de dados em memória. Se o aplicativo for reiniciado, a
solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, os dados
POST serão retornados para o aplicativo e tentem a solicitação GET novamente.

Valores retornados
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no
corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200
OK , supondo que não haja exceções sem tratamento. As exceções sem tratamento
são convertidas em erros 5xx.

Os tipos de retorno podem representar uma ampla gama de códigos de status HTTP.
Por exemplo, GET /todoitems/{id} pode retornar dois valores de status diferentes:

Se nenhum item corresponder à ID solicitada, o método retornará um código de


erro de status NotFound404 .
Caso contrário, o método retorna 200 com um JScorpo de resposta ON. Retornar
item resulta em uma resposta HTTP 200.

Examinar o ponto de extremidade PUT


O aplicativo de exemplo implementa um único ponto de extremidade PUT usando
MapPut :

C#

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
});

Esse método é semelhante ao MapPost método , exceto que ele usa HTTP PUT. Uma
resposta bem-sucedida retorna 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 ponto de extremidade PUT
Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que
o aplicativo é iniciado. Deverá haver um item no banco de dados antes de você fazer
uma chamada PUT. Chame GET para garantir que haja um item no banco de dados
antes de fazer uma chamada PUT.

Atualize o item de tarefas pendentes que tem ID = 1 e defina seu nome como "feed
fish" :

JSON

{
"id": 1,
"name": "feed fish",
"isComplete": false
}

Examinar e testar o ponto de extremidade


DELETE
O aplicativo de exemplo implementa um único ponto de extremidade DELETE usando
MapDelete :

C#

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}

return Results.NotFound();
});

Use o Postman para excluir um item pendente:

Defina o método como DELETE .


Defina o URI do objeto para excluir (por exemplo
https://localhost:5001/todoitems/1 , ).
Selecione Enviar.
Usar a API do MapGroup
O código do aplicativo de exemplo repete o prefixo de todoitems URL sempre que
configura um ponto de extremidade. As APIs Web geralmente têm grupos de pontos de
extremidade com um prefixo de URL comum e o MapGroup método está disponível
para ajudar a organizar esses grupos. Ele reduz o código repetitivo e permite
personalizar grupos inteiros de pontos de extremidade com uma única chamada para
métodos como RequireAuthorization e WithMetadata.

Substitua o conteúdo de Program.cs pelo seguinte código:

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>


await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>


await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();

return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}

return Results.NotFound();
});

app.Run();

O código anterior tem as seguintes alterações:

Adiciona var todoItems = app.MapGroup("/todoitems"); para configurar o grupo


usando o prefixo /todoitems de URL .
Altera todos os app.Map<HttpVerb> métodos para todoItems.Map<HttpVerb> .
Remove o prefixo /todoitems de URL das chamadas de Map<HttpVerb> método.

Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.

Usar a API TypedResults


Os Map<HttpVerb> métodos podem chamar métodos de manipulador de rotas em vez
de usar lambdas. Para ver um exemplo, atualize Program.cs com o seguinte código:

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Where(t =>
t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return TypedResults.Created($"/todoitems/{todo.Id}", todo);


}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return TypedResults.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}
return TypedResults.NotFound();
}

O Map<HttpVerb> código agora chama métodos em vez de lambdas:

C#

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

Esses métodos retornam objetos que implementam IResult e são definidos por
TypedResults:

C#

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Where(t =>
t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return TypedResults.Created($"/todoitems/{todo.Id}", todo);


}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return TypedResults.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}

return TypedResults.NotFound();
}

Os testes de unidade podem chamar esses métodos e testar se eles retornam o tipo
correto. Por exemplo, se o método for GetAllTodos :

C#

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

O código de teste de unidade pode verificar se um objeto do tipo Ok<Todo[]> é


retornado do método de manipulador. Por exemplo:

C#

public async Task GetAllTodos_ReturnsOkOfTodosResult()


{
// Arrange
var db = CreateDbContext();

// Act
var result = await TodosApi.GetAllTodos(db);

// Assert: Check for the correct returned type


Assert.IsType<Ok<Todo[]>>(result);
}
Impedir o excesso de postagem
Atualmente, o aplicativo de exemplo expõe todo Todo o objeto. Normalmente, os
aplicativos de produção limitam os dados que são de entrada e retornados usando um
subconjunto do modelo. Há várias razões por trás disso e a segurança é uma das
principais. O subconjunto de um modelo geralmente é chamado de DTO (Objeto de
Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado
neste artigo.

Um DTO pode ser usado para:

Impedir o excesso de postagem.


Ocultar propriedades que os clientes não devem exibir.
Omita algumas propriedades para reduzir o tamanho da carga.
Nivelar grafos de objeto que contêm objetos aninhados. Grafos de objeto
mesclados podem ser mais convenientes para os clientes.

Para demonstrar a abordagem de DTO, atualize a Todo classe para incluir um campo
secreto:

C#

public class Todo


{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}

O campo de segredo precisa estar oculto deste aplicativo, mas um aplicativo


administrativo pode optar por expô-lo.

Verifique se você pode postar e obter o campo de segredo.

Crie um arquivo chamado TodoItemDTO.cs com o seguinte código:

C#

public class TodoItemDTO


{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }

public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name,
todoItem.IsComplete);
}

Atualize o código em Program.cs para usar este modelo de DTO:

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

RouteGroupBuilder todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Select(x => new
TodoItemDTO(x)).ToArrayAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)


{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};

db.Todos.Add(todoItem);
await db.SaveChangesAsync();

return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);


}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO,
TodoDb db)
{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return TypedResults.NotFound();

todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}

return TypedResults.NotFound();
}

Verifique se você pode postar e obter todos os campos, exceto o campo de segredo.

Diferenças entre APIs mínimas e APIs com


controladores
APIs mínimas têm:

Suporte diferente para filtros. Para obter mais informações, consulte Filtros em
aplicativos de API mínimos
Não há suporte para associação de modelo, por exemplo: IModelBinderProvider,
IModelBinder. O suporte pode ser adicionado com um shim de associação
personalizado.
Não há suporte para associação de formulários, exceto para IFormFile. Para
obter mais informações, consulte Uploads de arquivo usando IFormFile e
IFormFileCollection.
Não há suporte interno para validação, ou seja, IModelValidator
Não há suporte para partes de aplicativo ou o modelo de aplicativo. Não há como
aplicar ou criar suas próprias convenções.
Sem suporte interno de renderização de exibição. É recomendável usar Razor o
Pages para renderizar exibições.
Sem suporte para JsonPatch
Sem suporte para OData

Próximas etapas

Configurar JSopções de serialização ON


Para obter informações sobre como configurar JSa serialização ON em seus aplicativos
de API Mínima, consulte Configurar JSopções de serialização ON.

Lidar com erros e exceções


A página de exceção do desenvolvedor é habilitada por padrão no ambiente de
desenvolvimento para aplicativos mínimos de API. Para obter informações sobre como
lidar com erros e exceções, consulte Manipular erros em ASP.NET Core APIs Web.

Testar aplicativos mínimos de API


Para obter um exemplo de teste de um aplicativo de API mínimo, consulte este exemplo
do GitHub .

Usar OpenAPI (Swagger)


Para obter informações sobre como usar o OpenAPI com aplicativos de API mínimos,
consulte Suporte a OpenAPI em APIs mínimas.

Publicar no Azure
Para obter informações sobre como implantar no Azure, consulte Início Rápido:
Implantar um aplicativo Web ASP.NET.

Saiba mais
Para obter mais informações sobre aplicativos mínimos de API, consulte Referência
rápida de APIs mínimas.
Criar uma API Web com o ASP.NET Core
e o MongoDB
Artigo • 10/01/2023 • 23 minutos para o fim da leitura

Por Pratik Khandelwal e Scott Addie

Este tutorial cria uma API Web que executa operações CRUD (Criar, Ler, Atualizar e
Excluir) em um banco de dados NoSQL do MongoDB .

Neste tutorial, você aprenderá como:

" Configurar o MongoDB
" Criar um banco de dados do MongoDB
" Definir uma coleção e um esquema do MongoDB
" Executar operações CRUD do MongoDB a partir de uma API Web
" Personalizar JSa serialização ON

Pré-requisitos
MongoDB
MongoDB Shell

Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Configurar o MongoDB
No Windows, o MongoDB é instalado em C:\Arquivos de Programas\MongoDB por
padrão. Adicione C:\Program Files\MongoDB\Server\<version_number>\bin à variável de
Path ambiente. Essa alteração possibilita o acesso ao MongoDB a partir de qualquer

lugar em seu computador de desenvolvimento.

Use o Shell do MongoDB instalado anteriormente nas etapas a seguir para criar um
banco de dados, criar coleções e armazenar documentos. Para obter mais informações
sobre comandos do Shell do MongoDB, consulte mongosh .
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.

Console

mongod --dbpath <data_directory_path>

3. Abra outra instância do shell de comando. Conecte-se ao banco de dados de


testes padrão executando o seguinte comando:

Console

mongosh

4. Execute o seguinte comando em um shell de comando:

Console

use BookStore

Um banco de dados chamado BookStore será criado se ele ainda não existir. 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:

Console

db.createCollection('Books')

O seguinte resultado é exibido:

Console

{ "ok" : 1 }

6. Defina um esquema para a coleção Books e insira dois documentos usando o


seguinte comando:

Console
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" }])

Um resultado semelhante ao seguinte é exibido:

Console

{
"acknowledged" : true,
"insertedIds" : [
ObjectId("61a6058e6c43f32854e51f51"),
ObjectId("61a6058e6c43f32854e51f52")
]
}

7 Observação

Os ObjectId s mostrados no resultado anterior não corresponderão aos


mostrados no shell de comando.

7. Visualize os documentos no banco de dados usando o seguinte comando:

Console

db.Books.find().pretty()

Um resultado semelhante ao seguinte é exibido:

Console

{
"_id" : ObjectId("61a6058e6c43f32854e51f51"),
"Name" : "Design Patterns",
"Price" : 54.93,
"Category" : "Computers",
"Author" : "Ralph Johnson"
}
{
"_id" : ObjectId("61a6058e6c43f32854e51f52"),
"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.

Criar o projeto da API Web do ASP.NET Core


Visual Studio

1. Vá para Arquivo>Novo>Projeto.

2. Selecione o tipo de projeto ASP.NET Core API Web e selecione Avançar.

3. Nomeie o projeto BookStoreApi e selecione Avançar.

4. Selecione a estrutura .NET 6.0 (suporte a longo prazo) e selecione Criar.

5. Na janela Console do Gerenciador de Pacotes, navegue até a raiz do projeto.


Execute o seguinte comando para instalar o driver .NET para MongoDB:

PowerShell

Install-Package MongoDB.Driver

Adicionar um modelo de entidade


1. Adicione um diretório Modelos à raiz do projeto.

2. Adicione uma classe Book ao diretório Modelos com o seguinte código:

C#

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace BookStoreApi.Models;

public class Book


{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }

[BsonElement("Name")]
public string BookName { get; set; } = null!;
public decimal Price { get; set; }

public string Category { get; set; } = null!;

public string Author { get; set; } = null!;


}

Na classe anterior, a Id propriedade é:

Necessário para mapear o objeto CLR (Common Language Runtime) para a


coleção MongoDB.
Anotado com [BsonId] para tornar essa propriedade a chave primária do
documento.
Anotado com [BsonRepresentation(BsonType.ObjectId)] para permitir a
passagem do parâmetro como tipo string em vez de uma estrutura
ObjectId . O Mongo processa a conversão de string para ObjectId .

A BookName propriedade é anotada com o [BsonElement] atributo . O valor do


atributo de Name representa o nome da propriedade da coleção do MongoDB.

Adicionar um modelo de configuração


1. Adicione os seguintes valores de configuração de banco de dados a
appsettings.json :

JSON

{
"BookStoreDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "BookStore",
"BooksCollectionName": "Books"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

2. Adicione uma classe BookStoreDatabaseSettings ao diretório Modelos com o


seguinte código:

C#
namespace BookStoreApi.Models;

public class BookStoreDatabaseSettings


{
public string ConnectionString { get; set; } = null!;

public string DatabaseName { get; set; } = null!;

public string BooksCollectionName { get; set; } = null!;


}

A classe anterior BookStoreDatabaseSettings é usada para armazenar os


appsettings.json valores de propriedade do BookStoreDatabase arquivo. Os

JSnomes das propriedades ON e C# são nomeados de forma idêntica para facilitar


o processo de mapeamento.

3. Adicione o código realçado a seguir a Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

No código anterior, a instância de configuração à qual a appsettings.json seção


do BookStoreDatabase arquivo é associada é registrada no contêiner DI (Injeção de
Dependência). Por exemplo, a BookStoreDatabaseSettings propriedade do
ConnectionString objeto é preenchida com a BookStoreDatabase:ConnectionString

propriedade em appsettings.json .

4. Adicione o seguinte código à parte superior de Program.cs para resolver a


BookStoreDatabaseSettings referência:

C#

using BookStoreApi.Models;

Adicionar um serviço de operações CRUD


1. Adicione um diretório Serviços à raiz do projeto.

2. Adicione uma classe BooksService ao diretório Serviços com o seguinte código:


C#

using BookStoreApi.Models;
using Microsoft.Extensions.Options;
using MongoDB.Driver;

namespace BookStoreApi.Services;

public class BooksService


{
private readonly IMongoCollection<Book> _booksCollection;

public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);

var mongoDatabase = mongoClient.GetDatabase(


bookStoreDatabaseSettings.Value.DatabaseName);

_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}

public async Task<List<Book>> GetAsync() =>


await _booksCollection.Find(_ => true).ToListAsync();

public async Task<Book?> GetAsync(string id) =>


await _booksCollection.Find(x => x.Id ==
id).FirstOrDefaultAsync();

public async Task CreateAsync(Book newBook) =>


await _booksCollection.InsertOneAsync(newBook);

public async Task UpdateAsync(string id, Book updatedBook) =>


await _booksCollection.ReplaceOneAsync(x => x.Id == id,
updatedBook);

public async Task RemoveAsync(string id) =>


await _booksCollection.DeleteOneAsync(x => x.Id == id);
}

No código anterior, uma BookStoreDatabaseSettings instância é recuperada do DI


por meio de injeção de construtor. Essa técnica fornece acesso aos
appsettings.json valores de configuração que foram adicionados na seção
Adicionar um modelo de configuração .

3. Adicione o código realçado a seguir a Program.cs :

C#
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

builder.Services.AddSingleton<BooksService>();

No código anterior, a classe BooksService é registrada com a DI para dar suporte à


injeção de construtor nas classes consumidoras. O tempo de vida do serviço
singleton é mais apropriado porque BooksService usa uma dependência direta de
MongoClient . De acordo com as diretrizes oficiais de reutilização do Cliente

Mongo , MongoClient deve ser registrado na DI com um tempo de vida de


serviço singleton.

4. Adicione o seguinte código à parte superior de Program.cs para resolver a


BooksService referência:

C#

using BookStoreApi.Services;

A BooksService classe usa os seguintes MongoDB.Driver membros para executar


operações CRUD no 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:

C#

public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);

var mongoDatabase = mongoClient.GetDatabase(


bookStoreDatabaseSettings.Value.DatabaseName);

_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}

IMongoDatabase : representa o banco de dados Mongo para executar


operações. Este tutorial usa o método genérico GetCollection<TDocument>
(collection) na interface para obter acesso aos dados em uma coleção específica.
Execute operações CRUD na coleção depois que esse método for chamado. Na
chamada de método GetCollection<TDocument>(collection) :
collection representa o nome da coleção.

TDocument representa o tipo de objeto CLR armazenado na coleção.

GetCollection<TDocument>(collection) retorna um objeto MongoCollection que


representa a coleção. Neste tutorial, os seguintes métodos são invocados na coleção:

DeleteOneAsync : exclui um único documento que corresponde aos critérios de


pesquisa fornecidos.
Encontrar< TDocument> : retorna todos os documentos na coleção que
correspondem aos critérios de pesquisa fornecidos.
InsertOneAsync : insere o objeto fornecido como um novo documento na
coleção.
ReplaceOneAsync : substitui o único documento que corresponde aos critérios
de pesquisa fornecidos pelo objeto fornecido.

Adicionar um controlador
Adicione uma classe BooksController ao diretório Controladores com o seguinte código:

C#

using BookStoreApi.Models;
using BookStoreApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace BookStoreApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
private readonly BooksService _booksService;

public BooksController(BooksService booksService) =>


_booksService = booksService;

[HttpGet]
public async Task<List<Book>> Get() =>
await _booksService.GetAsync();

[HttpGet("{id:length(24)}")]
public async Task<ActionResult<Book>> Get(string id)
{
var book = await _booksService.GetAsync(id);
if (book is null)
{
return NotFound();
}

return book;
}

[HttpPost]
public async Task<IActionResult> Post(Book newBook)
{
await _booksService.CreateAsync(newBook);

return CreatedAtAction(nameof(Get), new { id = newBook.Id },


newBook);
}

[HttpPut("{id:length(24)}")]
public async Task<IActionResult> Update(string id, Book updatedBook)
{
var book = await _booksService.GetAsync(id);

if (book is null)
{
return NotFound();
}

updatedBook.Id = book.Id;

await _booksService.UpdateAsync(id, updatedBook);

return NoContent();
}

[HttpDelete("{id:length(24)}")]
public async Task<IActionResult> Delete(string id)
{
var book = await _booksService.GetAsync(id);

if (book is null)
{
return NotFound();
}

await _booksService.RemoveAsync(id);

return NoContent();
}
}

O controlador da API Web anterior:

Usa a BooksService classe para executar operações CRUD.


Contém métodos de ação para dar suporte a solicitações GET, POST, PUT e DELETE
HTTP.
Chama o CreatedAtAction no método de ação Create para retornar uma resposta
HTTP 201 . O código de status 201 é a resposta padrão para um método HTTP
POST que cria um recurso no servidor. CreatedAtAction também adiciona um
cabeçalho Location à resposta. O cabeçalho Location especifica o URI do livro
recém-criado.

Testar a API Web


1. Compile e execute o aplicativo.

2. Navegue até https://localhost:<port>/api/books , em que <port> é o número da


porta atribuído automaticamente para o aplicativo, para testar o método de ação
sem Get parâmetros do controlador. Uma JSresposta ON semelhante à seguinte é
exibida:

JSON

[
{
"id": "61a6058e6c43f32854e51f51",
"bookName": "Design Patterns",
"price": 54.93,
"category": "Computers",
"author": "Ralph Johnson"
},
{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}
]

3. Navegue até https://localhost:<port>/api/books/{id here} para testar o método


de ação Get sobrecarregado do controlador. Uma JSresposta ON semelhante à
seguinte é exibida:

JSON

{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}

Configurar JSopções de serialização ON


Há dois detalhes a serem alterados sobre as JSrespostas ON retornadas na seção Testar
a API Web :

O uso de maiúsculas e minúsculas padrão dos nomes da propriedade deve ser


alterado para corresponder ao uso de maiúsculas e minúsculas Pascal dos nomes
de propriedade do objeto CLR.
A propriedade bookName deve ser retornada como Name .

Para cumprir os requisitos anteriores, faça as seguintes alterações:

1. Em Program.cs , encadeie o seguinte código realçado para a chamada de método


AddControllers :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

builder.Services.AddSingleton<BooksService>();

builder.Services.AddControllers()
.AddJsonOptions(
options => options.JsonSerializerOptions.PropertyNamingPolicy =
null);

Com a alteração anterior, os nomes de propriedade na resposta ON serializada


JSda API Web correspondem aos nomes de propriedade correspondentes no tipo
de objeto CLR. Por exemplo, a Book propriedade da Author classe serializa como
Author em vez de author .

2. Em Models/Book.cs , anote a BookName propriedade com o [JsonPropertyName]


atributo :

C#

[BsonElement("Name")]
[JsonPropertyName("Name")]
public string BookName { get; set; } = null!;

O [JsonPropertyName] valor do atributo de Name representa o nome da


propriedade na resposta ON serializada JSda API Web.

3. Adicione o seguinte código à parte superior de Models/Book.cs para resolver a


referência de [JsonProperty] atributo:

C#

using System.Text.Json.Serialization;

4. Repita as etapas definidas na seção Testar a API Web. Observe a diferença nos
JSnomes de propriedade ON.

Adicionar suporte de autenticação a uma API


Web
Identity ASP.NET Core adiciona a funcionalidade de logon da interface do usuário para
ASP.NET Core aplicativos Web. Para proteger APIs Web e SPAs, use um dos seguintes:

Azure Active Directory


Azure Active Directory B2C (Azure AD B2C)
Servidor duende Identity

O Servidor Duende Identity é uma estrutura OpenID Connect e OAuth 2.0 para ASP.NET
Core. O Servidor Duende Identity habilita os seguintes recursos de segurança:

Autenticação como serviço (AaaS)


SSO (logon único) em vários tipos de aplicativo
Controle de acesso para APIs
Gateway de Federação

) Importante

O Software Duende pode exigir que você pague uma taxa de licença pelo uso de
produção do Duende Identity Server. Para obter mais informações, consulte Migrar
do ASP.NET Core 5.0 para o 6.0.

Para obter mais informações, consulte a documentação do Duende Identity Server (site
do Duende Software) .
Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Criar APIs Web com o ASP.NET Core
Tipos de retorno de ação do controlador na API Web do ASP.NET Core
Criar uma API Web com o ASP.NET Core
Tutorial: Chamar uma API Web ASP.NET
Core com JavaScript
Artigo • 10/01/2023 • 11 minutos para o fim da leitura

De Rick Anderson

Este tutorial mostra como chamar uma API Web do ASP.NET Core com JavaScript,
usando a API Fetch .

Pré-requisitos
Tutorial completo: Criar uma API Web
Familiaridade com CSS, HTML e JavaScript

Chamar a API Web com o JavaScript


Nesta seção, você adicionará uma página HTML que contém formulários para criar e
gerenciar itens de tarefas pendentes. Manipuladores de eventos são anexados aos
elementos na página. Os manipuladores de eventos resultam em solicitações HTTP para
os métodos de ação da API Web. A função fetch da API Fetch inicia cada solicitação
HTTP.

A função fetch retorna um objeto Promise , que contém uma resposta HTTP
representada como um objeto Response . Um padrão comum é extrair o corpo da
JSresposta ON invocando a json função no Response objeto . O JavaScript atualiza a
página com os detalhes da resposta da API Web.

A chamada fetch mais simples aceita um parâmetro único que representa a rota. Um
segundo parâmetro, conhecido como objeto init , é opcional. init é usado para
configurar a solicitação HTTP.

1. Configure o aplicativo para fornecer arquivos estáticos e habilitar o mapeamento


de arquivo padrão. O código realçado a seguir é necessário em Program.cs :

C#

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));

var app = builder.Build();

if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

1. Crie uma pasta wwwroot na raiz do projeto.

2. Crie uma pasta css dentro da pasta wwwroot .

3. Crie uma pasta js dentro da pasta wwwroot .

4. Adicione um arquivo HTML chamado index.html à pasta wwwroot . Substitua o


conteúdo de index.html pela seguinte marcação:

HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<link rel="stylesheet" href="css/site.css" />
</head>
<body>
<h1>To-do CRUD</h1>
<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="editForm">
<h3>Edit</h3>
<form action="javascript:void(0);" onsubmit="updateItem()">
<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">&#10006;</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="js/site.js" asp-append-version="true"></script>


<script type="text/javascript">
getItems();
</script>
</body>
</html>

5. Adicione um arquivo CSS chamado site.css à pasta wwwroot/css . Substitua o


conteúdo de site.css pelos seguintes estilos:

css

input[type='submit'], button, [aria-label] {


cursor: pointer;
}

#editForm {
display: none;
}

table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}

th {
background-color: #f8f8f8;
padding: 5px;
}

td {
border: 1px solid;
padding: 5px;
}

6. Adicione um arquivo JavaScript chamado site.js à pasta wwwroot/js . Substitua o


conteúdo de site.js pelo seguinte código:

JavaScript

const uri = 'api/todoitems';


let todos = [];

function getItems() {
fetch(uri)
.then(response => response.json())
.then(data => _displayItems(data))
.catch(error => console.error('Unable to get items.', error));
}

function addItem() {
const addNameTextbox = document.getElementById('add-name');

const item = {
isComplete: false,
name: addNameTextbox.value.trim()
};

fetch(uri, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(response => response.json())
.then(() => {
getItems();
addNameTextbox.value = '';
})
.catch(error => console.error('Unable to add item.', error));
}

function deleteItem(id) {
fetch(`${uri}/${id}`, {
method: 'DELETE'
})
.then(() => getItems())
.catch(error => console.error('Unable to delete item.', error));
}

function displayEditForm(id) {
const item = todos.find(item => item.id === id);
document.getElementById('edit-name').value = item.name;
document.getElementById('edit-id').value = item.id;
document.getElementById('edit-isComplete').checked = item.isComplete;
document.getElementById('editForm').style.display = 'block';
}

function updateItem() {
const itemId = document.getElementById('edit-id').value;
const item = {
id: parseInt(itemId, 10),
isComplete: document.getElementById('edit-isComplete').checked,
name: document.getElementById('edit-name').value.trim()
};

fetch(`${uri}/${itemId}`, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(() => getItems())
.catch(error => console.error('Unable to update item.', error));

closeInput();

return false;
}

function closeInput() {
document.getElementById('editForm').style.display = 'none';
}

function _displayCount(itemCount) {
const name = (itemCount === 1) ? 'to-do' : 'to-dos';

document.getElementById('counter').innerText = `${itemCount}
${name}`;
}

function _displayItems(data) {
const tBody = document.getElementById('todos');
tBody.innerHTML = '';

_displayCount(data.length);

const button = document.createElement('button');

data.forEach(item => {
let isCompleteCheckbox = document.createElement('input');
isCompleteCheckbox.type = 'checkbox';
isCompleteCheckbox.disabled = true;
isCompleteCheckbox.checked = item.isComplete;
let editButton = button.cloneNode(false);
editButton.innerText = 'Edit';
editButton.setAttribute('onclick', `displayEditForm(${item.id})`);

let deleteButton = button.cloneNode(false);


deleteButton.innerText = 'Delete';
deleteButton.setAttribute('onclick', `deleteItem(${item.id})`);

let tr = tBody.insertRow();

let td1 = tr.insertCell(0);


td1.appendChild(isCompleteCheckbox);

let td2 = tr.insertCell(1);


let textNode = document.createTextNode(item.name);
td2.appendChild(textNode);

let td3 = tr.insertCell(2);


td3.appendChild(editButton);

let td4 = tr.insertCell(3);


td4.appendChild(deleteButton);
});

todos = data;
}

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:

1. Abra Properties\launchSettings.json.
2. Remova a launchUrl propriedade para forçar o aplicativo a abrir em index.html —
o arquivo padrão do projeto.

Esta amostra chama todos os métodos CRUD da API Web. A seguir, são apresentadas
explicações sobre as solicitações de API Web.

Obter uma lista de itens pendentes


No código a seguir, uma solicitação HTTP GET é enviada para a rota api/todoitems :

JavaScript

fetch(uri)
.then(response => response.json())
.then(data => _displayItems(data))
.catch(error => console.error('Unable to get items.', error));
Quando a API Web retorna um código de status bem-sucedido, a função _displayItems
é invocada. Cada item de tarefas pendentes no parâmetro de matriz aceito por
_displayItems é adicionado a uma tabela com os botões Editar e Excluir. Se a

solicitação da API Web falhar, um erro será registrado no console do navegador.

Adicionar um item pendente


No seguinte código:

Uma variável item é declarada para construir uma representação literal de objeto
do item de tarefas pendentes.
Uma solicitação Fetch é configurada com as seguintes opções:
method — especifica o verbo da ação HTTP POST.
body — especifica a JSrepresentação ON do corpo da solicitação. O JSON é

produzido passando o literal de objeto armazenado em item para a JSfunção


ON.stringify .
headers — especifica os Accept cabeçalhos de solicitação HTTP e Content-Type

. Ambos os cabeçalhos são definidos como application/json para especificar o


tipo de mídia que está sendo recebido e enviado, respectivamente.
Uma solicitação HTTP POST é enviada para a rota api/todoitems .

JavaScript

function addItem() {
const addNameTextbox = document.getElementById('add-name');

const item = {
isComplete: false,
name: addNameTextbox.value.trim()
};

fetch(uri, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(response => response.json())
.then(() => {
getItems();
addNameTextbox.value = '';
})
.catch(error => console.error('Unable to add item.', error));
}
Quando a API Web retorna um código de status de êxito, a função getItems é invocada
para atualizar a tabela HTML. Se a solicitação da API Web falhar, um erro será registrado
no console do navegador.

Atualizar um item pendente


A atualização de um item de tarefas pendentes é semelhante à adição de um; no
entanto, há duas diferenças significativas:

A rota é sufixada com o identificador exclusivo do item a ser atualizado. Por


exemplo, api/todoitems/1.
O verbo de ação HTTP é PUT, conforme indicado pela opção method .

JavaScript

fetch(`${uri}/${itemId}`, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(() => getItems())
.catch(error => console.error('Unable to update item.', error));

Excluir um item pendente


Para excluir um item de tarefas pendentes, defina a opção method da solicitação como
DELETE e especifique o identificador exclusivo do item na URL.

JavaScript

fetch(`${uri}/${id}`, {
method: 'DELETE'
})
.then(() => getItems())
.catch(error => console.error('Unable to delete item.', error));

Avance para o próximo tutorial para saber como gerar páginas de ajuda da API Web:

Introdução ao Swashbuckle e ao ASP.NET Core


Criar serviços de back-end para
aplicativos móveis nativos com o
ASP.NET Core
Artigo • 28/11/2022 • 9 minutos para o fim da leitura

Por James Montemagno

Os aplicativos móveis podem se comunicar com os serviços de back-end do ASP.NET


Core. Para obter instruções sobre como conectar os serviços Web locais dos simuladores
do iOS e dos emuladores do Android, confira Conectar-se aos Serviços Web Locais em
simuladores do iOS e emuladores do Android.

Exibir ou baixar o código de exemplo dos serviços de back-end

Exemplo do aplicativo móvel nativo


Este tutorial demonstra como criar serviços de back-end usando ASP.NET Core para dar
suporte a aplicativos móveis nativos. Ele usa o aplicativo TodoRest do Xamarin.Forms
como seu cliente nativo, que inclui clientes nativos separados para Android, iOS e
Windows. Siga o tutorial com links para criar o aplicativo nativo (e instale as ferramentas
do Xamarin gratuitas necessárias), além de baixar a solução de exemplo do Xamarin. O
exemplo de Xamarin inclui um projeto de serviços de API Web ASP.NET Core, que o
aplicativo ASP.NET Core deste artigo substitui (sem nenhuma alteração exigida pelo
cliente).
Recursos
O aplicativo TodoREST dá suporte à listagem, adição, exclusão e atualização To-Do
itens. 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:
Para testá-lo por conta própria no aplicativo ASP.NET Core criado na próxima seção em
execução no computador, atualize a constante do RestUrl aplicativo.

Os emuladores android não são executados no computador local e usam um IP de


loopback (10.0.2.2) para se comunicar com o computador local. Aproveite o DeviceInfo
do Xamarin.Essentials para detectar qual operação o sistema está executando para usar
a URL correta.

Navegue até o TodoREST projeto e abra o Constants.cs arquivo. O Constants.cs


arquivo contém a configuração a seguir.

C#
using Xamarin.Essentials;
using Xamarin.Forms;

namespace TodoREST
{
public static class Constants
{
// URL of REST service
//public static string RestUrl =
"https://YOURPROJECT.azurewebsites.net:8081/api/todoitems/{0}";

// URL of REST service (Android does not use localhost)


// Use http cleartext for local deployment. Change to https for
production
public static string RestUrl = DeviceInfo.Platform ==
DevicePlatform.Android ? "http://10.0.2.2:5000/api/todoitems/{0}" :
"http://localhost:5000/api/todoitems/{0}";
}
}

Opcionalmente, você pode implantar o serviço Web em um serviço de nuvem, como o


Azure, e atualizá-lo RestUrl .

Criando o projeto ASP.NET Core


Crie um novo aplicativo Web do ASP.NET Core no Visual Studio. Escolha o modelo de
API Web. Nomeie o projeto TodoAPI.
O aplicativo deve responder a todas as solicitações feitas à porta 5000, incluindo tráfego
http de texto claro para nosso cliente móvel. A atualização Startup.cs não é
UseHttpsRedirection executada no desenvolvimento:

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// For mobile apps, allow http traffic.
app.UseHttpsRedirection();
}

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

7 Observação

Execute o aplicativo diretamente, em vez de atrás de IIS Express. IIS Express ignora
solicitações não locais por padrão. Execute a execução do dotnet em um prompt
de comando ou escolha o perfil de nome do aplicativo na lista suspensa Destino de
Depuração na barra de ferramentas do Visual Studio.

Adicione uma classe de modelo para representar itens pendentes. Marque campos
necessários com o [Required] atributo:

C#

using System.ComponentModel.DataAnnotations;

namespace TodoAPI.Models
{
public class TodoItem
{
[Required]
public string ID { get; set; }

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

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

public bool Done { get; set; }


}
}

Os métodos da API exigem alguma maneira de trabalhar com dados. Use a mesma
interface ITodoRepository nos usos de exemplo originais do Xamarin:

C#

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:

C#

using System.Collections.Generic;
using System.Linq;
using TodoAPI.Interfaces;
using TodoAPI.Models;

namespace TodoAPI.Services
{
public class TodoRepository : ITodoRepository
{
private List<TodoItem> _todoList;

public TodoRepository()
{
InitializeData();
}

public IEnumerable<TodoItem> All


{
get { return _todoList; }
}

public bool DoesItemExist(string id)


{
return _todoList.Any(item => item.ID == id);
}

public TodoItem Find(string id)


{
return _todoList.FirstOrDefault(item => item.ID == id);
}

public void Insert(TodoItem item)


{
_todoList.Add(item);
}

public void Update(TodoItem item)


{
var todoItem = this.Find(item.ID);
var index = _todoList.IndexOf(todoItem);
_todoList.RemoveAt(index);
_todoList.Insert(index, item);
}

public void Delete(string id)


{
_todoList.Remove(this.Find(id));
}

private void InitializeData()


{
_todoList = new List<TodoItem>();

var todoItem1 = new TodoItem


{
ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
Name = "Learn app development",
Notes = "Take Microsoft Learn Courses",
Done = true
};

var todoItem2 = new TodoItem


{
ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
Name = "Develop apps",
Notes = "Use Visual Studio and Visual Studio for Mac",
Done = false
};

var todoItem3 = new TodoItem


{
ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
Name = "Publish apps",
Notes = "All app stores",
Done = false,
};

_todoList.Add(todoItem1);
_todoList.Add(todoItem2);
_todoList.Add(todoItem3);
}
}
}

Configure a implementação em Startup.cs :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddSingleton<ITodoRepository, TodoRepository>();
services.AddControllers();
}
Criando o controlador
Adicione um novo controlador ao projeto, TodoItemsController . Ele deve herdar de
ControllerBase. 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 runtime, esta instância será fornecida com
suporte do framework parainjeção de dependência.

C#

[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly ITodoRepository _todoRepository;

public TodoItemsController(ITodoRepository todoRepository)


{
_todoRepository = todoRepository;
}

Essa API é compatível com quatro verbos HTTP diferentes para executar operações
CRUD (Criar, Ler, Atualizar, Excluir) na fonte de dados. A mais simples delas é a operação
Read, que corresponde a uma solicitação HTTP GET.

Lendo itens
A solicitação de uma lista de itens é feita com uma solicitação GET ao método List . O
atributo [HttpGet] no método List indica que esta ação só deve lidar com as
solicitações GET. A rota para esta ação é a rota especificada no controlador. Você não
precisa necessariamente usar o nome da ação como parte da rota. Você precisa garantir
que cada ação tem uma rota exclusiva e não ambígua. Os atributos de roteamento
podem ser aplicados nos níveis de método e controlador para criar rotas específicas.

C#

[HttpGet]
public IActionResult List()
{
return Ok(_todoRepository.All);
}
O List método retorna um código de resposta 200 OK e todos os itens Todo,
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 Create método tem um [HttpPost] atributo aplicado a ele e aceita uma TodoItem
instância. Como o item argumento é passado no corpo do POST, esse parâmetro
especifica o [FromBody] atributo.

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.

C#

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

O exemplo usa códigos enum de erro que são passados para o cliente móvel:

C#

public enum ErrorCode


{
TodoItemNameAndNotesRequired,
TodoItemIDInUse,
RecordNotFound,
CouldNotCreateItem,
CouldNotUpdateItem,
CouldNotDeleteItem
}

Teste a adição de novos itens usando o Postman escolhendo o verbo POST fornecendo
o novo objeto no JSformato ON 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).

C#

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

C#

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
try
{
var item = _todoRepository.Find(id);
if (item == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_todoRepository.Delete(id);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
}
return NoContent();
}

Observe que, ao testar a funcionalidade de exclusão, nada é necessário no Corpo da


solicitação.
Impedir o excesso de postagem
Atualmente, o aplicativo de exemplo expõe todo TodoItem o objeto. Normalmente, os
aplicativos de produção limitam os dados que são de entrada e retornados usando um
subconjunto do modelo. Há várias razões por trás disso e a segurança é uma das
principais. O subconjunto de um modelo geralmente é conhecido como um DTO
(Objeto de Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO
é usado neste artigo.

Um DTO pode ser usado para:

Impedir o excesso de postagem.


Ocultar propriedades que os clientes não devem exibir.
Omita algumas propriedades para reduzir o tamanho da carga.
Nivelar grafos de objeto que contêm objetos aninhados. Grafos de objeto
nivelados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem do DTO, consulte Impedir a postagem excessiva

Convenções de Web API comuns


À medida que você desenvolve serviços de back-end para seu aplicativo, desejará criar
um conjunto consistente de convenções ou políticas para lidar com preocupações
paralelas. Por exemplo, no serviço mostrado acima, as solicitações de registros
específicos que não foram encontrados receberam uma resposta NotFound , em vez de
uma resposta BadRequest . Da mesma forma, os comandos feitos para esse serviço que
passaram tipos associados a um modelo sempre verificaram ModelState.IsValid e
retornaram um BadRequest para tipos de modelo inválidos.

Depois de identificar uma diretiva comum para suas APIs, você geralmente pode
encapsulá-la em um filtro. Saiba mais sobre como encapsular políticas comuns da API
em aplicativos ASP.NET Core MVC.

Recursos adicionais
Xamarin.Forms: Autenticação de Serviço Web
Xamarin.Forms: consumir um RESTserviço Web ful
Consumir REST serviços Web em Aplicativos Xamarin
Criar uma API Web com o ASP.NET Core
Publicar uma API Web ASP.NET Core no
Azure Gerenciamento de API com o
Visual Studio
Artigo • 28/11/2022 • 4 minutos para o fim da leitura

Por Matt Soucoup

Neste tutorial, você aprenderá a criar um projeto de API Web ASP.NET Core usando o
Visual Studio, garantir que ele tenha suporte a OpenAPI e, em seguida, publicar a API
Web em Serviço de Aplicativo do Azure e no Azure Gerenciamento de API.

Configuração
Para concluir o tutorial, você precisará de uma conta do Azure.

Abra uma conta do Azure gratuita se você não tiver uma.

Criar uma API Web ASP.NET Core


O Visual Studio permite que você crie facilmente um novo projeto de API Web ASP.NET
Core a partir de um modelo. Siga estas instruções para criar um novo projeto de API
Web ASP.NET Core:

No menu Arquivo, selecione Novo>Projeto.


Insira API Web na caixa de pesquisa.
Selecione o modelo de API Web ASP.NET Core e selecione Avançar.
Na caixa de diálogo Configurar seu novo projeto, nomeie o projeto WeatherAPI e
selecione Avançar.
Na caixa de diálogo Informações adicionais:
Confirme se o Framework é o .NET 6.0 (suporte de longo prazo).
Confirme se a caixa de seleção Usar controladores (desmarcar para usar APIs
mínimas) está marcada.
Confirme se a caixa de seleção habilitar o suporte ao OpenAPI está marcada.
Selecione Criar.

Explore o código
As definições do Swagger permitem que Gerenciamento de API do Azure leia as
definições de API do aplicativo. Ao verificar a caixa de seleção Habilitar suporte ao
OpenAPI durante a criação do aplicativo, o Visual Studio adiciona automaticamente o
código para criar as definições do Swagger. Abra o Program.cs arquivo que mostra o
seguinte código:

C#

...

builder.Services.AddSwaggerGen();

...

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

...

Verifique se as definições do Swagger sempre são


geradas
O Gerenciamento de API do Azure precisa que as definições do Swagger sempre
estejam presentes, independentemente do ambiente do aplicativo. Para garantir que
eles sejam sempre gerados, mova-se para app.UseSwagger(); fora do if
(app.Environment.IsDevelopment()) bloco.

O código atualizado:

C#

...

app.UseSwagger();

if (app.Environment.IsDevelopment())
{
app.UseSwaggerUI();
}

...
Alterar o roteamento de API
Altere a estrutura de URL necessária para acessar a ação Get do
WeatherForecastController . Conclua as seguintes etapas:

1. Abra o arquivo WeatherForecastController.cs .

2. Substitua o [Route("[controller]")] atributo de nível de classe por [Route("/")] .


A definição de classe atualizada:

C#

[ApiController]
[Route("/")]
public class WeatherForecastController : ControllerBase

Publicar a API Web para Serviço de Aplicativo


do Azure
Conclua as seguintes etapas para publicar a API Web ASP.NET Core no Azure
Gerenciamento de API:

1. Publique o aplicativo de API para Serviço de Aplicativo do Azure.


2. Publique o aplicativo de API Web ASP.NET Core na instância do serviço
Gerenciamento de API do Azure.

Publicar o aplicativo de API no Serviço de Aplicativo do


Azure
Conclua as seguintes etapas para publicar a API Web ASP.NET Core no Azure
Gerenciamento de API:

1. No Gerenciador de Soluções, clique com o botão direito do mouse no nome do


projeto e selecione Publicar.

2. Na caixa de diálogo Publicar , selecione Azure e selecione o botão Avançar .

3. Selecione Serviço de Aplicativo do Azure (Windows) e escolha o botão Avançar.

4. Selecione Criar um novo Serviço de Aplicativo do Azure.


A caixa de diálogo Criar Serviço de Aplicativo é exibida. 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.

5. Selecione o botão Criar.

6. Depois que o serviço de aplicativo for criado, selecione o botão Avançar .

7. Selecione Criar um novo serviço de Gerenciamento de API.

A caixa de diálogo Criar serviço Gerenciamento de API é exibida. Você pode


deixar os campos de entrada Nome da API, Nome da Assinatura e Grupo de
Recursos como eles são. Selecione o novo botão ao lado da entrada serviço
Gerenciamento de API e insira os campos necessários nessa caixa de diálogo.

Selecione o botão OK para criar o serviço Gerenciamento de API.

8. Selecione o botão Criar para continuar com a criação do serviço Gerenciamento de


API. Esse passo pode levar vários minutos para ser concluído.

9. Quando isso for concluído, selecione o botão Concluir .

10. A caixa de diálogo é fechada e uma tela de resumo é exibida com informações
sobre a publicação. Clique no botão Publicar.

A API Web publica no Serviço de Aplicativo do Azure e no Azure Gerenciamento


de API. Uma nova janela do navegador será exibida e mostrará a API em execução
no Serviço de Aplicativo do Azure. Você pode fechar essa janela.

11. Abra o portal do Azure em um navegador da Web e navegue até a instância


Gerenciamento de API que você criou.

12. Selecione a opção APIs no menu à esquerda.

13. Selecione a API que você criou nas etapas anteriores. Ele agora está preenchido e
você pode explorar ao redor.

Configurar o nome da API publicada


Observe que o nome da API é chamado WeatherAPI; no entanto, gostaríamos de
chamá-lo de Previsões meteorológicas. Conclua as seguintes etapas para atualizar o
nome:

1. Adicione o seguinte a Program.cs imediatamente após servies.AddSwaggerGen();


C#

builder.Services.ConfigureSwaggerGen(setup =>
{
setup.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "Weather Forecasts",
Version = "v1"
});
});

2. Republice a API Web ASP.NET Core e abra a instância de Gerenciamento de API do


Azure no portal do Azure.

3. Atualize a página no navegador. Você verá que o nome da API agora está correto.

Verificar se a API Web está funcionando


Você pode testar a API Web ASP.NET Core implantada no Azure Gerenciamento de API
do portal do Azure com as seguintes etapas:

1. Abra a guia Testar.


2. Selecione / ou a operação Obter .
3. Selecione Enviar.

Limpeza
Quando terminar de testar o aplicativo, vá para o portal do Azure e exclua o
aplicativo.

1. Selecione Grupos de recursos e, em seguida, selecione o grupo de recursos criado.

2. Na página Grupos de recursos, selecione Excluir.

3. Insira o nome do grupo de recursos e selecione Excluir. O aplicativo e todos os


outros recursos criados neste tutorial agora foram excluídos do Azure.

Recursos adicionais
Gerenciamento de API do Azure
Serviço de Aplicativo do Azure
Tutorial: Introdução ao ASP.NET Core
SignalR
Artigo • 02/12/2022 • 14 minutos para o fim da leitura

Este tutorial ensina os conceitos básicos da criação de um aplicativo em tempo real


usando SignalR. Você aprenderá como:

" Crie um projeto Web.


" Adicione a biblioteca de SignalR clientes.
" Criar um SignalR hub.
" Configure o projeto para usar 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 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um projeto do aplicativo Web


Visual Studio

1. Inicie o Visual Studio 2022 e selecione Criar um novo projeto.

2. Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core


Aplicativo Web e, em seguida, selecione Avançar.

3. Na caixa de diálogo Configurar seu novo projeto , insira SignalRChat para


Nome do projeto. É importante nomear o chat do projetoSignalR, incluindo a
correspondência da capitalização, para que os namespaces correspondam
quando você copiar e colar o código de exemplo.

4. Selecione Avançar.

5. Na caixa de diálogo Informações adicionais , selecione .NET 6.0 (suporte a


longo prazo) e, em seguida, selecione Criar.

Adicionar a SignalR biblioteca de clientes


A SignalR biblioteca de servidores está incluída na estrutura compartilhada ASP.NET
Core. A biblioteca de clientes do JavaScript não é incluída automaticamente no projeto.
Para este tutorial, use o Gerenciador de Bibliotecas (LibMan) para obter a biblioteca de
clientes do unpkg . unpkg é uma rede de distribuição de conteúdo global rápida para
tudo no npm .

Visual Studio

No Gerenciador de Soluções, clique com o botão direito do mouse no projeto


e selecione Adicionar>Biblioteca do Lado do Cliente.
Na caixa de diálogo Adicionar Biblioteca Client-Side :
Selecionar unpkg para Provedor
Inserir @microsoft/signalr@latest para Biblioteca
Selecione Escolher arquivos específicos, expanda a pasta dist/browser e
selecione signalr.js e signalr.min.js .
Definir Local de Destino como wwwroot/js/signalr/
Selecione Instalar

O LibMan cria uma pasta wwwroot/js/signalr e copia os arquivos selecionados para


ele.

Criar um SignalR hub


Um hub é uma classe que funciona como um pipeline de alto nível que lida com a
comunicação entre cliente e servidor.

Na pasta Projeto de SignalRchat, crie uma pasta Hubs .


Na pasta Hubs , crie a ChatHub classe com o seguinte código:

C#

using Microsoft.AspNetCore.SignalR;

namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}

A ChatHub classe herda da SignalRHub classe . 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. SignalR O código é assíncrono para fornecer
escalabilidade máxima.

configurar SignalR
O SignalR servidor deve ser configurado para passar SignalR solicitações para SignalR.
Adicione o código realçado a seguir ao Program.cs arquivo .

C#

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();
O código realçado anterior adiciona SignalR ao ASP.NET Core sistemas de injeção e
roteamento de dependência.

Adicionar SignalR código do cliente


Substitua o conteúdo em Pages/Index.cshtml pelo seguinte código:

CSHTML

@page
<div class="container">
<div class="row">&nbsp;</div>
<div class="row">
<div class="col-2">User</div>
<div class="col-4"><input type="text" id="userInput" />
</div>
</div>
<div class="row">
<div class="col-2">Message</div>
<div class="col-4"><input type="text" id="messageInput" />
</div>
</div>
<div class="row">&nbsp;</div>
<div class="row">
<div class="col-6">
<input type="button" id="sendButton" value="Send
Message" />
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>

A marcação anterior:
Cria caixas de texto e um botão enviar.
Cria uma lista com id="messagesList" para exibir mensagens recebidas do
SignalR hub.
Inclui referências de script para SignalR e o código do chat.js aplicativo é
criado na próxima etapa.
Na pasta wwwroot/js , crie um chat.js arquivo com o seguinte código:

JavaScript

"use strict";

var connection = new


signalR.HubConnectionBuilder().withUrl("/chatHub").build();

//Disable the send button until connection is established.


document.getElementById("sendButton").disabled = true;

connection.on("ReceiveMessage", function (user, message) {


var li = document.createElement("li");
document.getElementById("messagesList").appendChild(li);
// We can assign user-supplied strings to an element's textContent
because it
// is not interpreted as markup. If you're assigning in any other
way, you
// should be aware of possible script injection concerns.
li.textContent = `${user} says ${message}`;
});

connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});

document.getElementById("sendButton").addEventListener("click",
function (event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, message).catch(function
(err) {
return console.error(err.toString());
});
event.preventDefault();
});

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

Execute o aplicativo
Visual Studio
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.

 Dica

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 da direcionada. Nesse caso, a referência a

esse arquivo não funcionará e ocorrerá um erro 404 no console.

Se você receber o erro ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY no


Chrome, execute estes comandos para atualizar seu certificado de
desenvolvimento:
CLI do .NET

dotnet dev-certs https --clean


dotnet dev-certs https --trust

Publicar no Azure
Para obter informações sobre como implantar no Azure, confira Início Rápido: Implantar
um aplicativo Web ASP.NET.
Tutorial: Introdução ao ASP.NET Core
SignalR usando TypeScript e Webpack
Artigo • 04/01/2023 • 34 minutos para o fim da leitura

Por Sébastien Sougnez e Scott Addie

Este tutorial demonstra o uso do Webpack em um aplicativo Web ASP.NET Core


SignalR para agrupar e criar um cliente escrito no TypeScript . O Webpack habilita os
desenvolvedores a agrupar e criar recursos de um aplicativo Web do lado do cliente.

Neste tutorial, você aprenderá como:

" Criar um aplicativo ASP.NET Core SignalR


" Configurar o SignalR servidor
" Configurar um pipeline de build usando o Webpack
" Configurar o SignalR cliente TypeScript
" Habilitar a comunicação entre o cliente e o servidor

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

Pré-requisitos
Node.js com npm

Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar o aplicativo Web do ASP.NET Core


Visual Studio

Por padrão, o Visual Studio usa a versão do npm encontrada no diretório de


instalação. Para configurar o Visual Studio para procurar npm na variável de PATH
ambiente:

1. Inicie o Visual Studio. Na janela inicial, selecione Continuar sem código.


2. Navegue até projetos deopções> de ferramentas>e ferramentas web
externasdegerenciamento> depacotes web web de soluções>.

3. Selecione a $(PATH) entrada na lista. Selecione a seta para cima para mover a
entrada para a segunda posição na lista e selecione OK:

Para criar um novo aplicativo Web ASP.NET Core:

1. Use a opçãode menu Novo>Projeto de Arquivo> e escolha o modelo


ASP.NET Core Vazio. Selecione Avançar.
2. Nomeie o projeto SignalRWebpack e selecione Criar.
3. Selecione .NET 6.0 (Long-term support) na lista suspensa da Estrutura .
Selecione Criar.

Adicione o pacote NuGet Microsoft.TypeScript.MSBuild ao projeto:

1. Em Gerenciador de Soluções, clique com o botão direito do mouse no nó do


projeto e selecione Gerenciar Pacotes NuGet. Na guia Procurar , pesquise
Microsoft.TypeScript.MSBuild e selecione Instalar à direita para instalar o

pacote.

O Visual Studio adiciona o pacote NuGet no nó Dependências em Gerenciador de


Soluções, habilitando a compilação typeScript no projeto.

Configurar o servidor
Nesta seção, você configurará o aplicativo Web ASP.NET Core para enviar e receber
SignalR mensagens.

1. In Program.cs , call AddSignalR:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();

2. Novamente, em Program.cs , chamar UseDefaultFiles e UseStaticFiles:

C#

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

O código anterior permite que o servidor localize e atenda ao index.html arquivo.


O arquivo é servido se o usuário insere sua URL completa ou a URL raiz do
aplicativo Web.

3. Crie um novo diretório nomeado Hubs na raiz SignalRWebpack/ do projeto, para a


SignalR classe hub.

4. Crie um novo arquivo, Hubs/ChatHub.cs com o seguinte código:

C#

using Microsoft.AspNetCore.SignalR;

namespace SignalRWebpack.Hubs;

public class ChatHub : Hub


{
public async Task NewMessage(long 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 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 em SendAsyncClients.All. As mensagens recebidas são
enviadas a todos os clientes conectados ao hub.

5. Adicione a seguinte using instrução na parte superior Program.cs para resolver a


ChatHub referência:

C#

using SignalRWebpack.Hubs;

6. In Program.cs , mapeie a /hub rota para o ChatHub hub. Substitua o código exibido
Hello World! pelo seguinte código:

C#

app.MapHub<ChatHub>("/hub");

Configurar o cliente
Nesta seção, você criará um projeto Node.js para converter o TypeScript em
JavaScript e agrupar recursos do lado do cliente, incluindo HTML e CSS, usando o
Webpack.

1. Execute o seguinte comando na raiz do projeto para criar um package.json


arquivo:

Console

npm init -y

2. Adicione a propriedade realçada ao package.json arquivo e salve as alterações de


arquivo:

JSON

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

Console

npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-


css-extract-plugin ts-loader typescript webpack webpack-cli

A -E opção desabilita o comportamento padrão do npm de gravar operadores de


intervalo de controle de versão semântico para package.json . Por exemplo,
"webpack": "5.70.0" é usado em vez de "webpack": "^5.70.0" . Essa opção evita

atualizações não intencionais para versões de pacote mais recentes.

Para obter mais informações, consulte a documentação de instalação do npm .

4. Substitua a scripts propriedade do package.json arquivo pelo seguinte código:

JSON

"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},

Os seguintes scripts são definidos:

build : agrupa os recursos do lado do cliente no modo de desenvolvimento e

observa as alterações de arquivo. O observador de arquivos faz com que o


lote se regenere toda vez que um arquivo de projeto é alterado. A opção
mode desabilita otimizações de produção, como tree shaking e minificação.

usar build somente em desenvolvimento.


release : agrupa os recursos do lado do cliente no modo de produção.

publish : executa o script release para agrupar recursos do lado do cliente


no modo de produção. Ele chama o comando de publicação da CLI do .NET
para publicar o aplicativo.

5. Crie um arquivo nomeado webpack.config.js na raiz do projeto, com o seguinte


código:

JavaScript

const path = require("path");


const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

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(),
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
filename: "css/[name].[chunkhash].css",
}),
],
};

O arquivo anterior configura o processo de compilação do Webpack:

A output propriedade substitui o valor padrão de dist . Em vez disso, o


pacote é emitido no wwwroot diretório.
A resolve.extensions matriz inclui .js importar o JavaScript do SignalR
cliente.
6. Copie o src diretório do projeto de exemplo para a raiz do projeto. O src
diretório contém os seguintes arquivos:

index.html , que define a marcação clichê da página inicial:

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ASP.NET Core SignalR with TypeScript and
Webpack</title>
</head>
<body>
<div id="divMessages" class="messages"></div>
<div class="input-zone">
<label id="lblMessage" for="tbMessage">Message:</label>
<input id="tbMessage" class="input-zone-input" type="text"
/>
<button id="btnSend">Send</button>
</div>
</body>
</html>

css/main.css , que fornece estilos CSS para a página inicial:

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

tsconfig.json , que configura o compilador TypeScript para produzir


JavaScript compatível com ECMAScript 5:

JSON

{
"compilerOptions": {
"target": "es5"
}
}

index.ts :

TypeScript

import * as signalR from "@microsoft/signalr";


import "./css/main.css";

const divMessages: HTMLDivElement =


document.querySelector("#divMessages");
const tbMessage: HTMLInputElement =
document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement =
document.querySelector("#btnSend");
const username = new Date().getTime();

const connection = new signalR.HubConnectionBuilder()


.withUrl("/hub")
.build();

connection.on("messageReceived", (username: string, message:


string) => {
const m = document.createElement("div");

m.innerHTML = `<div class="message-author">${username}</div>


<div>${message}</div>`;
divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});

connection.start().catch((err) => document.write(err));

tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {


if (e.key === "Enter") {
send();
}
});

btnSend.addEventListener("click", send);

function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => (tbMessage.value = ""));
}

O código anterior recupera referências a elementos DOM e anexa dois


manipuladores de eventos:
keyup : é acionado quando o usuário digita na tbMessage caixa de texto e

chama a send função quando o usuário pressiona a tecla Enter .


click : é acionado quando o usuário seleciona o botão Enviar e a função

de chamadas send é chamada.

A classe HubConnectionBuilder cria um novo construtor para configurar a


conexão do servidor. A função withUrl configura a URL do hub.

SignalR permite a troca de mensagens entre um cliente e um servidor. Cada


mensagem tem um nome específico. Por exemplo, mensagens com o nome
messageReceived podem executar a lógica responsável por exibir a nova
mensagem na zona de mensagens. É possível escutar uma mensagem
específica por meio da função on . Qualquer número de nomes de mensagem
pode ser ouvido. Também é possível passar parâmetros para a mensagem,
como o nome do autor e o conteúdo da mensagem recebida. Quando o
cliente recebe a mensagem, um novo elemento div é criado com o nome do
autor e o conteúdo da mensagem em seu atributo innerHTML . Ele é
adicionado ao elemento principal div que exibe as mensagens.

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.

7. Execute o seguinte comando na raiz do projeto:

Console

npm i @microsoft/signalr @types/node

O comando anterior é instalado:

O SignalR cliente TypeScript , que permite que o cliente envie mensagens


para o servidor.
As definições de tipo TypeScript para Node.js, que permite a verificação em
tempo de compilação de tipos de Node.js.

Testar o aplicativo
Confirme se o aplicativo funciona com as seguintes etapas:

Visual Studio

1. Execute o Webpack no release modo. 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.

Console

npm run release

Esse comando gera os ativos do lado do cliente a serem atendidos ao


executar o aplicativo. Os ativos são colocados na wwwroot pasta.

O Webpack concluiu as seguintes tarefas:

Limpou o conteúdo do wwwroot diretório.


Converteu o TypeScript em JavaScript em um processo conhecido como
transpilação.
Mangled o JavaScript gerado para reduzir o tamanho do arquivo em um
processo conhecido como minificação.
Copiou os arquivos JavaScript, CSS e HTML processados do src wwwroot
diretório.
Injetou os seguintes elementos no wwwroot/index.html arquivo:
Uma <link> marca, fazendo referência ao wwwroot/main.<hash>.css
arquivo. Essa marca é colocada imediatamente antes do fim da marca
</head> .

Uma <script> marca, fazendo referência ao arquivo minificado


wwwroot/main.<hash>.js . Essa marca é colocada imediatamente antes

do fim da marca </body> .

2. Selecione Iniciar de Depuração>sem depuração para iniciar o aplicativo em


um navegador sem anexar o depurador. O wwwroot/index.html arquivo é
servido em https://localhost:<port> .

Se você receber erros de compilação, tente fechar e reabrir a solução.

3. Abra outra instância de navegador (qualquer navegador) e cole a URL na barra


de endereços.

4. Escolha qualquer navegador, digite algo na caixa de texto Mensagem e


selecione o botão Enviar . O nome de usuário exclusivo e a mensagem são
exibidas em ambas as páginas instantaneamente.

Recursos adicionais
SignalR ASP.NET Core cliente JavaScript
Usar hubs no ASP.NET Core SignalR
Usar ASP.NET Core SignalR com Blazor
Artigo • 28/11/2022 • 68 minutos para o fim da leitura

Este tutorial ensina os conceitos básicos da criação de um aplicativo em tempo real


usando SignalR com Blazoro .

Saiba como:

" Criar um Blazor projeto


" Adicionar a SignalR biblioteca de clientes
" Adicionar um SignalR hub
" Adicionar SignalR serviços e um ponto de extremidade para o SignalR hub
" Adicionar Razor código de componente para chat

No final deste tutorial, você terá um aplicativo de chat funcional.

Pré-requisitos
Visual Studio

Visual Studio 2022 ou posterior com a carga de trabalho ASP.NET e


desenvolvimento para a Web
SDK do .NET 6.0

Aplicativo de exemplo
O download do aplicativo de chat de exemplo do tutorial não é necessário para este
tutorial. O aplicativo de exemplo é o aplicativo final e funcional produzido seguindo as
etapas deste tutorial.

Exibir ou baixar o código de exemplo

Criar um Blazor Server aplicativo


Siga as diretrizes para sua escolha de ferramentas:

Visual Studio
7 Observação

O Visual Studio 2022 ou posterior e o SDK do .NET Core 6.0.0 ou posterior são
necessários.

1. Criar um novo projeto.

2. Selecione o Blazor Server Modelo de aplicativo. Selecione Avançar.

3. Digite BlazorServerSignalRApp o campo Nome do projeto. Confirme se a


entrada Local está correta ou forneça um local para o projeto. Selecione
Avançar.

4. Selecione Criar.

Adicionar a SignalR biblioteca de clientes


Visual Studio

1. Em Gerenciador de Soluções, clique com o botão direito do mouse no


BlazorServerSignalRApp projeto e selecione Gerenciar Pacotes NuGet.

2. Na caixa de diálogo Gerenciar Pacotes NuGet, confirme se a origem do


pacote está definida como nuget.org .

3. Com Procurar selecionado, digite Microsoft.AspNetCore.SignalR.Client na


caixa de pesquisa.

4. Nos resultados da pesquisa, selecione o


Microsoft.AspNetCore.SignalR.Client pacote. Defina a versão para
corresponder à estrutura compartilhada do aplicativo. Selecione Instalar.

5. Se a caixa de diálogo Visualizar Alterações for exibida, selecione OK.

6. Se a caixa de diálogo Aceitação da Licença for exibida, selecione Aceito se


você concordar com os termos de licença.

Adicionar um SignalR hub


Crie uma Hubs pasta (plural) e adicione a seguinte ChatHub classe ( Hubs/ChatHub.cs ):

C#

using Microsoft.AspNetCore.SignalR;

namespace BlazorServerSignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}

Adicionar serviços e um ponto de extremidade


para o SignalR hub
1. Abra o arquivo Program.cs .

2. Adicione os namespaces para Microsoft.AspNetCore.ResponseCompression e a


ChatHub classe à parte superior do arquivo:

C#

using Microsoft.AspNetCore.ResponseCompression;
using BlazorServerSignalRApp.Server.Hubs;

3. Adicione serviços middleware de compactação de resposta a Program.cs :

C#

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});

4. Em Program.cs :
Use o Middleware de Compactação de Resposta na parte superior da
configuração do pipeline de processamento.
Entre os pontos de extremidade para mapear o Blazor hub e o fallback do
lado do cliente, adicione um ponto de extremidade para o hub.

C#

app.UseResponseCompression();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToPage("/_Host");

app.Run();

Adicionar Razor código de componente para


chat
1. Abra o arquivo Pages/Index.razor .

2. Substitua a marcação pelo seguinte código:

razor

@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable

<PageTitle>Index</PageTitle>

<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>

@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();

hubConnection.On<string, string>("ReceiveMessage", (user,


message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});

await hubConnection.StartAsync();
}

private async Task Send()


{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput,
messageInput);
}
}

public bool IsConnected =>


hubConnection?.State == HubConnectionState.Connected;

public async ValueTask DisposeAsync()


{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}

7 Observação

Desabilite o middleware de compactação de resposta no Development ambiente ao


usar Recarga Dinâmica. Para obter mais informações, consulte ASP.NET Core
BlazorSignalR diretrizes.

Executar o aplicativo
Siga as diretrizes para suas ferramentas:

Visual Studio

1. Pressione F5 para executar o aplicativo com depuração ou Ctrl + F5

(Windows)/ ⌘ + F5 (macOS) para executar o aplicativo sem depuração.

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

3. Escolha qualquer navegador, insira um nome e uma mensagem e selecione o


botão para enviar a mensagem. O nome e a mensagem são exibidos em
ambas as páginas instantaneamente:

Citações: Star Trek VI: O País ©Desconhecido 1991 Paramount


Próximas etapas
Neste tutorial, você aprendeu a:

" Criar um Blazor projeto


" Adicionar a SignalR biblioteca de clientes
" Adicionar um SignalR hub
" Adicionar SignalR serviços e um ponto de extremidade para o SignalR hub
" Adicionar Razor código de componente para chat

Para saber mais sobre como criar Blazor aplicativos, consulte a Blazor documentação:

BlazorASP.NET Core

autenticação de token de portador com Identity Eventos de Servidor, WebSockets


e Server-Sent

Recursos adicionais
Hubs seguros SignalR para aplicativos hospedados Blazor WebAssembly
Visão geral do ASP.NET Core SignalR
SignalR negociação entre origens para autenticação
SignalR Configuração
Depurar ASP.NET Core Blazor WebAssembly
Diretrizes de mitigação de ameaças para ASP.NET Core Blazor Server
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Tutorial: Criar um cliente e um servidor
gRPC no ASP.NET Core
Artigo • 05/10/2022 • 33 minutos para o fim da leitura

Este tutorial mostra como criar um cliente gRPC do .NET Core e um servidor gRPC do
ASP.NET Core. No final, você terá um cliente gRPC que se comunica com o serviço de
Boas-vindas do gRPC.

Neste tutorial, você:

" Criará um servidor gRPC.


" Criará um cliente gRPC.
" Teste o cliente gRPC com o serviço gRPC Greeter.

Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um serviço gRPC


Visual Studio

Inicie o Visual Studio 2022 e selecione Criar um novo projeto.


Na caixa de diálogo Criar um novo projeto , pesquise gRPC por . Selecione
ASP.NET Core serviço gRPC e selecione Avançar.
Na caixa de diálogo Configurar seu novo projeto , insira GrpcGreeter o nome
do projeto. É importante nomear o grpcGreeter do projeto para que os
namespaces correspondam quando você copia e cola o código.
Selecione Avançar.
Na caixa de diálogo Informações adicionais , selecione .NET 6.0 (suporte a
longo prazo) e, em seguida, selecione Criar.
Executar o serviço

Visual Studio

Pressione Ctrl + F5 para execução sem o depurador.

O Visual Studio exibe a seguinte caixa de diálogo quando um projeto ainda


não está configurado para usar o SSL:

Selecione Sim se você confia no certificado SSL do IIS Express.

A seguinte caixa de diálogo é exibida:

Selecione Sim se você concordar com confiar no certificado de


desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, consulte o
Firefox SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

Visual Studio:
Inicia Kestrel o servidor.
Inicia um navegador.
Navega para http://localhost:port , como http://localhost:7042 .
porta: um número de porta atribuído aleatoriamente para o aplicativo.
localhost : o nome do host padrão para o computador local. Localhost

serve somente solicitações da Web do computador local.

Os logs mostram o serviço escutando https://localhost:<port> , onde <port> está o


número da porta localhost atribuído aleatoriamente quando o projeto é criado e
definido em Properties/launchSettings.json .

Console

info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:<port>
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development

7 Observação

O modelo gRPC é configurado para usar o protocolo TLS . Os clientes gRPC


precisam usar HTTPS para chamar o servidor. O número da porta localhost do
serviço gRPC é atribuído aleatoriamente quando o projeto é criado e definido no
arquivo Properties\launchSettings.json do projeto de serviço gRPC.

O macOS não é compatível com gRPC do ASP.NET Core com TLS. É necessária uma
configuração adicional para executar com êxito os serviços gRPC no macOS. Para
obter mais informações, confira Não é possível iniciar o aplicativo ASP.NET Core
gRPC no macOS.

Examinar os arquivos de projeto


Arquivos de projeto GrpcGreeter:
Protos/greet.proto : define o Greeter gRPC e é usado para gerar os ativos do

servidor gRPC. Para obter mais informações, confira Introdução ao gRPC.


Services pasta: contém a implementação do Greeter serviço.

appSettings.json : contém dados de configuração, como o protocolo usado por


Kestrel. Para obter mais informações, consulte Configuração no ASP.NET Core.
Program.cs , que contém:

O ponto de entrada do serviço gRPC. Para saber mais, confira Host Genérico
.NET no ASP.NET Core.
Código que configura o comportamento do aplicativo. Para obter mais
informações, veja Inicialização do aplicativo.

Criar o cliente gRPC em um aplicativo de


console .NET
Visual Studio

Abra uma segunda instância do Visual Studio e selecione Criar um novo


projeto.
Na caixa de diálogo Criar um novo projeto , selecione Aplicativo de Console
e selecione Avançar.
Na caixa de texto Nome do Projeto , insira GrpcGreeterClient e
selecioneAvançar.
Na caixa de diálogo Informações Adicionais , selecione .NET 6.0 (suporte a
longo prazo) e selecione Criar.

Adicionar os pacotes NuGet necessários


O projeto do cliente gRPC requer os seguintes pacotes NuGet:

Grpc.Net.Client , que contém o cliente do .NET Core.


Google.Protobuf , que contém APIs de mensagem protobuf para C#.
Grpc.Tools , que contém suporte a ferramentas C# para arquivos protobuf. O
pacote de ferramentas não é necessário em runtime e, portanto, a dependência é
marcada com PrivateAssets="All" .

Visual Studio
Instalar os pacotes usando o PMC (Console do Gerenciador de Pacotes) ou
Gerenciar Pacotes NuGet.

Opção do PMC para instalar pacotes

No Visual Studio, selecione Ferramentasdo Console doGerenciador de


Pacotes>> NuGet

Na janela Console do Gerenciador de Pacotes , execute cd GrpcGreeterClient


para alterar diretórios para a pasta que contém os GrpcGreeterClient.csproj
arquivos.

Execute os seguintes comandos:

PowerShell

Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools

Opção Gerenciar Pacotes NuGet para instalar pacotes

Clique com o botão direito do mouse no projeto em pacotes NuGetdo


Gerenciador de Soluções> Manage.
Selecione a guia Procurar.
Insira Grpc.Net.Client na caixa de pesquisa.
Selecione o pacote Grpc.Net.Client na guia Procurar e selecione Instalar.
Repita para Google.Protobuf e Grpc.Tools .

Adicionar greet.proto
Crie uma pasta Protos no projeto do cliente gRPC.

Copie o arquivo Protos\greet.proto do serviço gRPC Greeter para a pasta Protos no


projeto do cliente gRPC.

Atualize o namespace dentro do greet.proto arquivo para o namespace do


projeto:
option csharp_namespace = "GrpcGreeterClient";

Edite o arquivo de GrpcGreeterClient.csproj projeto:

Visual Studio

Clique com o botão direito do mouse no projeto e selecione Editar Arquivo de


Projeto.

Adicione um grupo de itens com um elemento <Protobuf> que faça referência ao


arquivo greet.proto:

XML

<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

Criar o cliente Greeter


Crie o projeto cliente para criar os tipos no GrpcGreeterClient namespace.

7 Observação

Os tipos GrpcGreeterClient são gerados automaticamente pelo processo de build.


O pacote de ferramentas Grpc.Tools gera os seguintes arquivos com base no
arquivo greet.proto :

GrpcGreeterClient\obj\Debug\[TARGET_FRAMEWORK]\Protos\Greet.cs : o código

de buffer de protocolo que preenche, serializa e recupera os tipos de


mensagem de solicitação e resposta.
GrpcGreeterClient\obj\Debug\[TARGET_FRAMEWORK]\Protos\GreetGrpc.cs :

contém as classes de cliente geradas.

Para obter mais informações sobre os ativos C# gerados automaticamente pelo


Grpc.Tools , consulte os serviços gRPC com C#: ativos C# gerados.

Atualize o arquivo cliente Program.cs gRPC com o código a seguir.

C#
using System.Threading.Tasks;
using Grpc.Net.Client;
using GrpcGreeterClient;

// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();

No código realçado anterior, substitua o número 7042 da porta localhost pelo


número da HTTPS porta especificado no Properties/launchSettings.json projeto
de GrpcGreeter serviço.

Program.cs contém o ponto de entrada e a lógica para o cliente gRPC.

O cliente Greeter é criado da seguinte forma:

Criando uma instância de GrpcChannel que contém as informações para criar a


conexão com o serviço gRPC.
Como usar o GrpcChannel para construir o cliente Greeter:

C#

// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();

O cliente Greeter chama o método SayHello assíncrono. O resultado da chamada


SayHello é exibido:

C#

// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();

Testar o cliente gRPC com o serviço de Boas-


vindas do gRPC
Visual Studio

No serviço Greeter, pressione Ctrl+F5 para iniciar o servidor sem o


depurador.
No projeto GrpcGreeterClient , pressione Ctrl+F5 para iniciar o cliente sem o
depurador.

O cliente envia uma saudação para o serviço com uma mensagem contendo seu nome,
GreeterClient. O serviço envia a mensagem "Olá, GreeterClient" como uma resposta. A
resposta "Olá, GreeterClient" é exibida no prompt de comando:

Console

Greeting: Hello GreeterClient


Press any key to exit...

O serviço gRPC registra os detalhes da chamada bem-sucedida nos logs gravados no


prompt de comando:

Console

info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:<port>
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path:
C:\GH\aspnet\docs\4\Docs\aspnetcore\tutorials\grpc\grpc-
start\sample\GrpcGreeter
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 POST https://localhost:
<port>/Greet.Greeter/SayHello application/grpc
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'gRPC - /Greet.Greeter/SayHello'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'gRPC - /Greet.Greeter/SayHello'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 78.32260000000001ms 200 application/grpc

Atualize o appsettings.Development.json arquivo adicionando as seguintes linhas:

"Microsoft.AspNetCore.Hosting": "Information",
"Microsoft.AspNetCore.Routing.EndpointMiddleware": "Information"

7 Observação

O código neste artigo requer o certificado de desenvolvimento HTTPS do ASP.NET


Core para proteger o serviço gRPC. Se o cliente .NET gRPC falhar com a mensagem
The remote certificate is invalid according to the validation procedure. ou

The SSL connection could not be established. o certificado de desenvolvimento


não for confiável. Para corrigir esse problema, consulte Chamar um serviço gRPC
com um certificado não confiável/inválido.

2 Aviso

O ASP.NET Core gRPC tem requisitos extras para ser usado com Serviço de
Aplicativo do Azure ou o IIS. Para obter mais informações sobre onde o gRPC pode
ser usado, consulte gRPC em plataformas .NET com suporte.

Próximas etapas
Exiba ou baixe o código de exemplo concluído para este tutorial (como baixar).
Visão geral do gRPC no .NET
Serviços do gRPC com C#
Migrar gRPC de C-core para gRPC para .NET
RazorPáginas com o Entity Framework
Core em ASP.NET Core – Tutorial 1 de 8
Artigo • 05/10/2022 • 61 minutos para o fim da leitura

Por Tom Dykstra , Jeremy Likness e Jon P Smith

Este é o primeiro de uma série de tutoriais que mostram como usar o Entity Framework
(EF) Core em um aplicativo ASP.NET Core Razor Pages. O tutorial cria um site de uma
Contoso University fictícia. O site inclui funcionalidades como admissão de alunos,
criação de cursos e atribuições de instrutor. O tutorial usa a primeira abordagem de
código. Para obter informações sobre como seguir este tutorial usando a primeira
abordagem do banco de dados, consulte este problema do Github .

Baixe ou exiba o aplicativo concluído. Baixar instruções.

Pré-requisitos
Se você não estiver familiarizado Razorcom Razor o Pages, passe pela série de
tutoriais Introdução às Páginas antes de iniciar esta.

Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Mecanismos de banco de dados


As instruções do Visual Studio usam SQL Server LocalDB, uma versão do SQL Server
Express que é executada somente no Windows.

Solução de problemas
Se você encontrar um problema que não possa resolver, compare seu código com o
projeto concluído . Uma boa maneira de obter ajuda é postando uma pergunta para
StackOverflow.com, usando a marca ASP.NET Core ou a EF Core marca .

O aplicativo de exemplo
O aplicativo criado nesses tutoriais é um site básico de universidade. Os usuários podem
exibir e atualizar informações de alunos, cursos e instrutores. Veja a seguir algumas das
telas criadas no tutorial.
O estilo de interface do usuário deste site baseia-se nos modelos de projeto internos. O
foco do tutorial é como usar EF Core com ASP.NET Core, não como personalizar a
interface do usuário.

Opcional: compilar o download de exemplo


Esta etapa é opcional. A criação do aplicativo concluído é recomendada quando você
tem problemas que não pode resolver. Se você encontrar um problema que não possa
resolver, compare seu código com o projeto concluído . Baixar instruções.

Visual Studio

Selecione ContosoUniversity.csproj para abrir o projeto.

Compile o projeto.

No PMC (Console do Gerenciador de Pacotes), execute o seguinte comando:

PowerShell

Update-Database
Execute o projeto para propagar o banco de dados.

Criar o projeto de aplicativo Web


Visual Studio

1. Inicie o Visual Studio 2022 e selecione Criar um novo projeto.

2. Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core


Aplicativo Web e selecione Avançar.
3. Na caixa de diálogo Configurar seu novo projeto , insira ContosoUniversity o
nome do projeto. É importante nomear o projeto ContosoUniversity, incluindo
a correspondência da capitalização, para que os namespaces correspondam
quando você copiar e colar o código de exemplo.

4. Selecione Avançar.

5. Na caixa de diálogo Informações adicionais , selecione .NET 6.0 (suporte a


longo prazo) e, em seguida, selecione Criar.
Configurar o estilo do site
Copie e cole o seguinte código no Pages/Shared/_Layout.cshtml arquivo:

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
<link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-append-
version="true" />
</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-area="" asp-
page="/Index">Contoso University</a>
<button class="navbar-toggler" type="button" data-bs-
toggle="collapse" data-bs-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
justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/Students/Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/Courses/Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/Instructors/Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/Departments/Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2021 - Contoso University - <a asp-area="" asp-
page="/Privacy">Privacy</a>
</div>
</footer>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>

@await RenderSectionAsync("Scripts", required: false)


</body>
</html>

O arquivo de layout define o cabeçalho, o rodapé e o menu do site. O código anterior


faz as seguintes alterações:

Cada ocorrência de "ContosoUniversity" para "Contoso University". Há três


ocorrências.
As Home entradas e Privacy o menu são excluídos.
As inscrições são adicionadas para Sobre, Alunos, Cursos, Instrutores e
Departamentos.

In Pages/Index.cshtml , substitua o conteúdo do arquivo pelo seguinte código:

CSHTML

@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

<div class="row mb-auto">


<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 mb-4 ">
<p class="card-text">
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>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column position-static">
<p class="card-text mb-auto">
You can build the application by following the steps in
a series of tutorials.
</p>
<p>
@* <a
href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro"
class="stretched-link">See the tutorial</a>
*@ </p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column">
<p class="card-text mb-auto">
You can download the completed project from GitHub.
</p>
<p>
@* <a
href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef
-rp/intro/samples" class="stretched-link">See project source code</a>
*@ </p>
</div>
</div>
</div>
</div>

O código anterior substitui o texto sobre ASP.NET Core pelo texto sobre esse aplicativo.

Execute o aplicativo para verificar se o página inicial aparece.

O modelo de dados
As seções a seguir criam um modelo de dados:

Um aluno pode ser registrado em qualquer quantidade de cursos e um curso pode ter
qualquer quantidade de alunos registrados.

A entidade Student

Crie uma pasta Modelos na pasta do projeto.


Crie Models/Student.cs com o seguinte código:

C#

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }

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


}
}

A propriedade ID se torna a coluna de chave primária da tabela de banco de dados que


corresponde a essa classe. Por padrão, EF Core interpreta uma propriedade nomeada ID
ou classnameID como a chave primária. Portanto, o nome alternativo reconhecido
automaticamente para a chave primária da classe Student é StudentID . Para obter mais
informações, consulte EF Core - Chaves.

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 entidade Student armazena todas as
entidades Enrollment relacionadas àquele Aluno. Por exemplo, se uma linha Aluno no
banco de dados tiver duas linhas Registro relacionadas, a propriedade de navegação
Enrollments conterá duas entidades de Registro.

No banco de dados, uma linha registro estará relacionada a uma linha Student se sua
StudentID coluna contiver o valor da ID do aluno. Por exemplo, suponha que uma linha

de aluno tenha ID=1. As linhas de registro relacionadas terão StudentID = 1. StudentID


é uma chave estrangeira na tabela Registro.

A propriedade Enrollments é definida como ICollection<Enrollment> porque pode


haver várias entidades de registro relacionadas. Outros tipos de coleção podem ser
usados, como List<Enrollment> ou HashSet<Enrollment> . Quando
ICollection<Enrollment> é usado, EF Core cria uma HashSet<Enrollment> coleção por
padrão.

A entidade Enrollment
Crie Models/Enrollment.cs com o seguinte código:

C#

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

A propriedade EnrollmentID é a chave primária; essa entidade usa o padrão


classnameID , em vez de ID por si mesmo. Para um modelo de dados de produção,

muitos desenvolvedores escolhem um padrão e o usam consistentemente. Este tutorial


usa ambos apenas para ilustrar que os dois funcionam. Usar ID sem classname facilita a
implementação de alguns tipos de alterações 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 anulável. Uma nota nula é

diferente de uma nota zero — nulo significa que uma nota ainda não é conhecida ou
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 propriedade CourseID é uma chave estrangeira e a propriedade de navegação


correspondente é Course . Uma entidade Enrollment está associada a uma entidade
Course .

EF Core interpretará uma propriedade como uma chave estrangeira se ela for nomeada
<navigation property name><primary key property name> . Por exemplo, StudentID é a

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

Crie Models/Course.cs com o seguinte código:

C#

using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }

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


}
}
A propriedade Enrollments é uma propriedade de navegação. Uma entidade Course
pode estar relacionada a qualquer quantidade de entidades Enrollment .

O atributo DatabaseGenerated permite que o aplicativo especifique a chave primária em


vez de fazer com que ela seja gerada pelo banco de dados.

Crie o aplicativo. O compilador gera vários avisos sobre como null os valores são
tratados. Veja este problema do GitHub , tipos de referência anuláveis e Tutorial:
expresse sua intenção de design com mais clareza com tipos de referência anuláveis e
não anuláveis para obter mais informações.

Para eliminar os avisos de tipos de referência anuláveis, remova a seguinte linha do


ContosoUniversity.csproj arquivo:

XML

<Nullable>enable</Nullable>

Atualmente, o mecanismo de scaffolding não dá suporte a tipos de referência anuláveis,


portanto, os modelos usados no scaffold também não podem.

Remova a ? anotação de tipo de referência anulável para public string? RequestId {


get; set; } Pages/Error.cshtml.cs que o projeto seja compilado sem avisos do
compilador.

Aplicar scaffold a páginas de Aluno


Nesta seção, a ferramenta ASP.NET Core scaffolding é usada para gerar:

Uma EF Core DbContext classe. O contexto é a classe principal que coordena a


funcionalidade do Entity Framework para determinado modelo de dados. Ele
deriva da classe Microsoft.EntityFrameworkCore.DbContext.
Razor páginas que lidam com operações CRUD (Criar, Ler, Atualizar e Excluir) para
a Student entidade.

Visual Studio

Crie uma pasta Pages/Students.


No Gerenciador de Soluções, clique com o botão direito do mouse na pasta
Páginas/Alunos e selecione Adicionar>Novo Item com Scaffold.
Na caixa de diálogo Adicionar Novo Item de Scaffold :
Na guia à esquerda, selecione Páginas Comuns >Razor Instaladas >
Selecione Razor Páginas usando o CRUD (Entity Framework)>ADD.
Na caixa de diálogo Adicionar Razor Páginas usando o CRUD (Entity
Framework ):
Na lista suspensa classe Modelo, selecione Aluno
(ContosoUniversity.Models).
Na linha Classe de contexto de dados, selecione o sinal de + (adição).
Altere o nome do contexto de dados para terminar em SchoolContext
vez de ContosoUniversityContext . O nome do contexto atualizado:
ContosoUniversity.Data.SchoolContext
Selecione Adicionar para concluir a adição da classe de contexto de
dados.
Selecione Adicionar para concluir a caixa de diálogo Adicionar Razor
Páginas .

Os seguintes pacotes são instalados automaticamente:

Microsoft.EntityFrameworkCore.SqlServer

Microsoft.EntityFrameworkCore.Tools

Microsoft.VisualStudio.Web.CodeGeneration.Design

Se a etapa anterior falhar, crie o projeto e repita a etapa scaffold.

O processo de scaffolding:

Cria Razor páginas na pasta Páginas/Alunos :


Create.cshtml e Create.cshtml.cs
Delete.cshtml e Delete.cshtml.cs

Details.cshtml e Details.cshtml.cs

Edit.cshtml e Edit.cshtml.cs
Index.cshtml e Index.cshtml.cs

Cria Data/SchoolContext.cs .
Adiciona o contexto à injeção de dependência em Program.cs .
Adiciona uma cadeia de conexão de banco de dados a appsettings.json .

Cadeia de conexão de banco de dados


A ferramenta de scaffolding gera uma cadeia de conexão no appsettings.json arquivo.

Visual Studio
A cadeia de conexão especifica SQL Server LocalDB:

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=
(localdb)\\mssqllocaldb;Database=SchoolContext-
0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

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.
Por padrão, o LocalDB cria arquivos .mdf no diretório C:/Users/<user> .

Atualizar a classe do contexto de banco de


dados
A classe principal que coordena a EF Core funcionalidade de um determinado modelo
de dados é a classe de contexto do banco de dados. O contexto é derivado de
Microsoft.EntityFrameworkCore.DbContext. O contexto especifica quais entidades são
incluídas no modelo de dados. Neste projeto, a classe é chamada SchoolContext .

Atualize Data/SchoolContext.cs com o seguinte código:

C#

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions<SchoolContext> options)
: base(options)
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}

O código anterior muda do singular DbSet<Student> Student para o plural


DbSet<Student> Students . Para fazer com que o Razor código de Páginas corresponda

ao novo DBSet nome, faça uma alteração global de: _context.Student. Para:
_context.Students.

Há oito ocorrências.

Como um conjunto de entidades contém várias entidades, muitos desenvolvedores


preferem que os nomes de DBSet propriedades sejam plurais.

O código realçado:

Cria uma DbSet<TEntity> propriedade para cada conjunto de entidades. Na EF


Core terminologia:
Um conjunto de entidades normalmente corresponde a uma tabela de banco
de dados.
Uma entidade corresponde a uma linha da tabela.
Chama OnModelCreating. OnModelCreating :
É chamado quando SchoolContext foi inicializado, mas antes do modelo ser
bloqueado e usado para inicializar o contexto.
É necessário porque, posteriormente, no tutorial, a Student entidade terá
referências às outras entidades.

Esperamos corrigir esse problema em uma versão futura.

Module.vb
O ASP.NET Core é construído com a injeção de dependência. Serviços como o
SchoolContext são registrados com injeção de dependência durante a inicialização do

aplicativo. Componentes que exigem esses serviços, como Razor o Pages, recebem
esses serviços por meio de parâmetros de construtor. O código de construtor que
obtém uma instância de contexto do banco de dados é mostrado mais adiante no
tutorial.

A ferramenta de scaffolding registrou automaticamente a classe de contexto com o


contêiner de injeção de dependência.

Visual Studio

As seguintes linhas realçadas foram adicionadas pelo scaffolder:

C#

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));

O nome da cadeia de conexão é passado para o contexto com a chamada de um


método em um objeto DbContextOptions. Para desenvolvimento local, o sistema de
configuração ASP.NET Core lê a cadeia de conexão do arquivo ou da
appsettings.json appsettings.Development.json cadeia de conexão.

Adicionar o filtro de exceção do banco de dados


Adicione AddDatabaseDeveloperPageExceptionFilter e UseMigrationsEndPoint ,
conforme mostrado no código a seguir:

Visual Studio

C#

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}

Adicione o pacote NuGet


Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .

No Console do Gerenciador de Pacotes, insira o seguinte para adicionar o pacote


NuGet:

PowerShell

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

O Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacote NuGet fornece


ASP.NET Core middleware para páginas de erro do Entity Framework Core. Esse
middleware ajuda a detectar e diagnosticar erros com migrações do Entity Framework
Core.

Fornece AddDatabaseDeveloperPageExceptionFilter informações de erro úteis no


ambiente de desenvolvimento para erros de migrações de EF.

Criar o banco de dados


Atualize Program.cs para criar o banco de dados se ele não existir:

Visual Studio

C#
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}

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


{
var services = scope.ServiceProvider;

var context = services.GetRequiredService<SchoolContext>();


context.Database.EnsureCreated();
// DbInitializer.Initialize(context);
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O EnsureCreated método não tomará nenhuma ação se existir um banco de dados para
o contexto. Se não existir nenhum banco de dados, ele criará o banco de dados e o
esquema. EnsureCreated habilita o seguinte fluxo de trabalho para manipular alterações
no modelo de dados:
Exclua o banco de dados. Qualquer dado existente é perdido.
Altere o modelo de dados. Por exemplo, adicione um campo EmailAddress .
Execute o aplicativo.
EnsureCreated cria um banco de dados com o novo esquema.

Esse fluxo de trabalho funciona no início do desenvolvimento quando o esquema está


evoluindo rapidamente, desde que os dados não precisem ser preservados. A situação é
diferente quando os dados que foram inseridos no banco de dados precisam ser
preservados. Quando esse for o caso, use migrações.

Posteriormente, na série de tutoriais, o banco de dados é excluído pelo qual foi criado
EnsureCreated e as migrações são usadas. Um banco de dados criado pelo

EnsureCreated não pode ser atualizado usando migrações.

Testar o aplicativo
Execute o aplicativo.
Selecione o link Alunos e Criar Novo.
Teste os links Editar, Detalhes e Excluir.

Propagar o banco de dados


O método EnsureCreated cria um banco de dados vazio. Esta seção adiciona um código
que preenche o banco de dados com os dados de teste.

Crie Data/DbInitializer.cs com o seguinte código:

C#

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}

var students = new Student[]


{
new
Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.P
arse("2019-09-01")},
new
Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Pa
rse("2017-09-01")},
new
Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse
("2018-09-01")},
new
Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Pa
rse("2017-09-01")},
new
Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017
-09-01")},
new
Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Pars
e("2016-09-01")},
new
Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse
("2018-09-01")},
new
Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Pars
e("2019-09-01")}
};

context.Students.AddRange(students);
context.SaveChanges();

var courses = new Course[]


{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};

context.Courses.AddRange(courses);
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};

context.Enrollments.AddRange(enrollments);
context.SaveChanges();
}
}
}

O código verifica se há alunos no banco de dados. Se não houver nenhum aluno, ele
adicionará dados de teste ao banco de dados. Ele carrega os dados de teste em
matrizes, em vez de em coleções de List<T> , para otimizar o desempenho.

In Program.cs , remova // da DbInitializer.Initialize linha:

C#

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


{
var services = scope.ServiceProvider;

var context = services.GetRequiredService<SchoolContext>();


context.Database.EnsureCreated();
DbInitializer.Initialize(context);
}

Visual Studio

Interrompa o aplicativo se ele estiver em execução e execute o seguinte


comando no PMC (Console do Gerenciador de Pacotes):

PowerShell

Drop-Database -Confirm

Responda para Y excluir o banco de dados.

Reinicie o aplicativo.
Selecione a página Alunos para ver os dados propagados.

Exibir o banco de dados


Visual Studio

Abra o SSOX (Pesquisador de Objetos do SQL Server) no menu Exibir do


Visual Studio.
No SSOX, selecione (localdb)\MSSQLLocalDB > Databases > SchoolContext-
{GUID}. O nome do banco de dados é gerado a partir do nome de contexto
fornecido anteriormente, além de um traço e um GUID.
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.
Clique com o botão direito do mouse na tabela Aluno e clique em Exibir
Código para ver como o modelo Student é mapeado para o esquema de
tabela Student .

Métodos EF assíncronos em aplicativos Web


ASP.NET Core
A programação assíncrona é o modo padrão para 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 o
código síncrono, muitos threads podem estar vinculados enquanto não estão
funcionando 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 pode 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 , a palavra-chave


await e o método ToListAsync fazem o código ser executado de forma assíncrona.

C#
public async Task OnGetAsync()
{
Students = await _context.Students.ToListAsync();
}

A palavra-chave async instrui o compilador a:


Gerar retornos de chamada para partes do corpo do método.
Criar o objeto Task que é retornado.
O tipo retornado Task representa um trabalho em andamento.
A palavra-chave await faz com que o compilador divida o método em duas partes.
A primeira parte termina com a operação que é iniciada de forma assíncrona. A
segunda parte é colocada em um método de retorno de chamada que é chamado
quando a operação é concluída.
ToListAsync é a versão assíncrona do método de extensão ToList .

Algumas coisas a serem cientes ao escrever código assíncrono que usa EF Core:

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 ToListAsync ,
SingleOrDefaultAsync , FirstOrDefaultAsync e SaveChangesAsync . Isso não inclui

instruções que apenas alteram um IQueryable , como var students =


context.Students.Where(s => s.LastName == "Davolio") .

Um EF Core contexto não é thread safe: não tente fazer várias operações em
paralelo.
Para aproveitar os benefícios de desempenho do código assíncrono, verifique se
os pacotes de biblioteca (como para paginação) usam assíncronos se chamarem EF
Core métodos que enviam consultas para o banco de dados.

Para obter mais informações sobre a programação assíncrona, consulte Visão geral de
Async e Programação assíncrona com async e await.

Considerações sobre o desempenho


Em geral, uma página da Web não deve carregar um número arbitrário de linhas. Uma
consulta deve usar paginação ou uma abordagem limitante. Por exemplo, a consulta
anterior poderia ser usada Take para limitar as linhas retornadas:

C#

public async Task OnGetAsync()


{
Student = await _context.Students.Take(10).ToListAsync();
}

Enumerar uma tabela grande em um modo de exibição poderá retornar uma resposta
HTTP 200 parcialmente construída se uma exceção de banco de dados ocorrer em parte
por meio da enumeração.

A paginação será abordada posteriormente no tutorial.

Para obter mais informações, consulte as considerações de desempenho (EF).

Próximas etapas
Usar o SQLite para desenvolvimento, SQL Server para produção

Próximo tutorial
Parte 2, Razor Páginas com EF Core em
ASP.NET Core – CRUD
Artigo • 28/11/2022 • 34 minutos para o fim da leitura

Por Tom Dykstra , Jeremy Likness e Jon P Smith

O aplicativo Web da Contoso University demonstra como criar Razor aplicativos Web
pages usando EF Core e o Visual Studio. Para obter informações sobre a série de
tutoriais, consulte o primeiro tutorial.

Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e
compare esse código com o que você criou seguindo o tutorial.

Neste tutorial, o código CRUD (criar, ler, atualizar e excluir) gerado por scaffolding é
examinado e personalizado.

Nenhum repositório
Alguns desenvolvedores usam um padrão de camada de serviço ou repositório para
criar uma camada de abstração entre a interface do usuário (Razor Páginas) e a camada
de acesso a dados. Este tutorial não faz isso. Para minimizar a complexidade e manter o
tutorial focado EF Core, EF Core o código é adicionado diretamente às classes de
modelo de página.

Atualizar a página Detalhes


O código com scaffold das páginas Alunos não inclui dados de registro. Nesta seção, os
registros são adicionados à Details página.

Ler inscrições
Para exibir os dados de registro de um aluno na página, os dados de registro devem ser
lidos. O código scaffolded lê Pages/Students/Details.cshtml.cs apenas os Student
dados, sem os Enrollment dados:

C#

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


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

Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

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

Substitua o método OnGetAsync pelo código a seguir para ler os dados de registro para
o aluno selecionado. As alterações são realçadas.

C#

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


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

Student = await _context.Students


.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

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

Os Include métodos ThenInclude e ThenInclude fazem com que o contexto carregue a


Student.Enrollments propriedade de navegação e, em cada registro, a

Enrollment.Course propriedade de navegação. Esses métodos são examinados


detalhadamente no tutorial de dados relacionados à leitura .

O AsNoTracking método 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 inscrições
Substitua o código Pages/Students/Details.cshtml pelo código a seguir para exibir uma
lista de registros. As alterações são realçadas.

CSHTML

@page
@model ContosoUniversity.Pages.Students.DetailsModel

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

<h1>Details</h1>

<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.Enrollments)
</dt>
<dd class="col-sm-10">
<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>

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 Course entidade armazenada na Course propriedade de navegação da entidade
Registros.

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.

Maneiras de ler uma entidade


O código gerado usa FirstOrDefaultAsync para ler uma entidade. Esse método retornará
null se nada for encontrado; caso contrário, retornará a primeira linha encontrada que
atenda aos critérios de filtro de consulta. FirstOrDefaultAsync geralmente é uma opção
melhor do que as seguintes alternativas:

SingleOrDefaultAsync – gera uma exceção se houver mais de uma entidade que


atenda ao filtro de consulta. Para determinar se mais de uma linha poderia ser
retornada pela consulta, o SingleOrDefaultAsync tenta buscar várias linhas. Esse
trabalho extra será desnecessário se a consulta só puder retornar uma entidade,
como quando ela pesquisa em uma chave exclusiva.
FindAsync – localiza uma entidade com a PK (chave primária). Se uma entidade
com o PK estiver sendo controlada pelo contexto, ela será retornada sem uma
solicitação para o banco de dados. Esse método é otimizado para pesquisar uma
única entidade, mas você não pode chamar Include com FindAsync . Portanto, se
forem necessários dados relacionados, FirstOrDefaultAsync será a melhor opção.

Rotear dados versus cadeia de consulta


A URL para a página Detalhes é https://localhost:<port>/Students/Details?id=1 . O
valor da chave primária da entidade está na cadeia de consulta. Alguns desenvolvedores
preferem passar o valor da chave nos dados da rota: https://localhost:
<port>/Students/Details/1 . Para obter mais informações, confira Atualizar o código
gerado.

Atualizar a página Criar


O código OnPostAsync com scaffold para a página Criar é vulnerável à sobreposição.
Substitua o OnPostAsync método Pages/Students/Create.cshtml.cs pelo código a
seguir.

C#

public async Task<IActionResult> OnPostAsync()


{
var emptyStudent = new Student();

if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

return Page();
}

TryUpdateModelAsync
O código anterior cria um objeto Student e, em seguida, usa campos de formulário
postados para atualizar as propriedades do objeto Student. O método
TryUpdateModelAsync:

Usa os valores de formulário postados da PageContext propriedade no


PageModel.
Atualiza apenas as propriedades listadas ( s => s.FirstMidName, s => s.LastName,
s => s.EnrollmentDate ).
Procura campos de formulário com um prefixo "Student". Por exemplo,
Student.FirstMidName . Não diferencia maiúsculas de minúsculas.
Usa o sistema de model binding para converter valores de formulário de cadeias
de caracteres para os tipos no modelo Student . Por exemplo, EnrollmentDate é
convertido em DateTime .

Execute o aplicativo e crie uma entidade de aluno para testar a página Criar.

Excesso de postagem
O uso de TryUpdateModel para atualizar campos com valores postados é uma melhor
prática de segurança porque ele impede o excesso de postagem. Por exemplo, suponha
que a entidade Student inclua uma propriedade Secret que esta página da Web não
deve atualizar nem adicionar:

C#

public class Student


{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}

Mesmo que o aplicativo não tenha um Secret campo na Página de criação ou


atualização Razor , um hacker poderá definir o Secret valor por sobreposto. 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 banco de dados. A imagem a seguir mostra a ferramenta Fiddler
adicionando o Secret campo, com o valor "OverPost", aos valores de formulário
postados.

O valor "OverPost" foi adicionado com êxito à propriedade Secret da linha inserida. Isso
acontece embora o designer de aplicativo nunca tenha pretendido que a propriedade
Secret fosse definida com a página Criar.

Exibir modelo
Os modelos de exibição fornecem uma maneira alternativa para impedir o excesso de
postagem.

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 banco de dados. O modelo de exibição contém apenas as
propriedades necessárias para a página da 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 um


modelo de entrada para passar dados entre a Razor classe de modelo de página
Páginas e o navegador.

Considere o seguinte modelo de exibição StudentVM :

C#
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}

O seguinte código usa o modelo de exibição StudentVM para criar um novo aluno:

C#

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

public async Task<IActionResult> OnPostAsync()


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

var entry = _context.Add(new Student());


entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

O método SetValues define os valores desse objeto lendo valores de outro


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


Precisa ter propriedades que correspondam.

Usar StudentVM requer o uso StudentVM da página Criar em vez de Student :

CSHTML

@page
@model CreateVMModel

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

<h1>Create</h1>

<h4>Student</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="StudentVM.LastName" class="control-label">
</label>
<input asp-for="StudentVM.LastName" class="form-control" />
<span asp-validation-for="StudentVM.LastName" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.FirstMidName" class="control-
label"></label>
<input asp-for="StudentVM.FirstMidName" class="form-control"
/>
<span asp-validation-for="StudentVM.FirstMidName"
class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.EnrollmentDate" class="control-
label"></label>
<input asp-for="StudentVM.EnrollmentDate" class="form-
control" />
<span asp-validation-for="StudentVM.EnrollmentDate"
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");}
}

Atualizar a página Editar


In Pages/Students/Edit.cshtml.cs , substitua o OnGetAsync código a seguir e
OnPostAsync os métodos.

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

Student = await _context.Students.FindAsync(id);

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

public async Task<IActionResult> OnPostAsync(int id)


{
var studentToUpdate = await _context.Students.FindAsync(id);

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

if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

return Page();
}

As alterações de código são semelhantes à página Criar, com algumas exceções:

FirstOrDefaultAsync foi substituído por FindAsync. Quando você não precisa

incluir dados relacionados, FindAsync é mais eficiente.


OnPostAsync tem um parâmetro id .

O aluno atual é buscado do banco de dados, em vez de criar um aluno vazio.

Execute o aplicativo e teste-o criando e editando um aluno.

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. As informações de
acompanhamento 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
do banco de dados emite um comando SQL INSERT .

Uma entidade pode estar em um dos seguintes estados:

Added : a entidade ainda não existe no banco de dados. O SaveChanges método

emite uma instrução INSERT .

Unchanged : nenhuma alteração precisa ser salva com essa entidade. Uma entidade

tem esse status quando é lida do banco de dados.

Modified : alguns ou todos os valores de propriedade da entidade foram


modificados. O SaveChanges método emite uma instrução UPDATE .

Deleted : a entidade foi marcada para exclusão. O SaveChanges método emite uma
DELETE instrução.

Detached : a entidade não está sendo controlada pelo contexto do banco de dados.

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 SaveChanges gera uma instrução
SQL UPDATE que atualiza apenas as propriedades alteradas.

Em um aplicativo Web, o DbContext que lê uma entidade e exibe os dados é descartado


depois que uma página é renderizada. Quando o método OnPostAsync de uma página é
chamado, é feita uma nova solicitação da Web e com uma nova instância do DbContext .
A nova leitura da entidade nesse novo contexto simula o processamento da área de
trabalho.

Atualizar a página Excluir


Nesta seção, uma mensagem de erro personalizada é implementada quando a chamada
falha SaveChanges .

Substitua o código em Pages/Students/Delete.cshtml.cs pelo seguinte código:

C#
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
private readonly ILogger<DeleteModel> _logger;

public DeleteModel(ContosoUniversity.Data.SchoolContext context,


ILogger<DeleteModel> logger)
{
_context = context;
_logger = logger;
}

[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }

public async Task<IActionResult> OnGetAsync(int? id, bool?


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

Student = await _context.Students


.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

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

if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = String.Format("Delete {ID} failed. Try
again", id);
}

return Page();
}

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


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

var student = await _context.Students.FindAsync(id);

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

try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, ErrorMessage);

return RedirectToAction("./Delete",
new { id, saveChangesError = true });
}
}
}
}

O código anterior:

Adiciona log.
Adiciona o parâmetro saveChangesError opcional à assinatura do OnGetAsync
método. 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 são mais prováveis quando o banco de dados está na nuvem. O
saveChangesError parâmetro é false quando a página OnGetAsync Excluir é chamada
da interface do usuário. Quando OnGetAsync é chamado porque OnPostAsync a
operação de exclusão falhou, o saveChangesError parâmetro é true .

O método OnPostAsync 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 banco de dados é capturada.


O método OnGetAsync das páginas Excluir é chamado com saveChangesError=true .
Adicione uma mensagem de erro a Pages/Students/Delete.cshtml :

CSHTML

@page
@model ContosoUniversity.Pages.Students.DeleteModel

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

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

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


<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
</dl>

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

Execute o aplicativo e exclua um aluno para testar a página Excluir.

Próximas etapas
Tutorial anterior Próximo tutorial
Parte 3, Razor Páginas com EF Core em
ASP.NET Core – Classificar, Filtrar,
Paginar
Artigo • 28/11/2022 • 31 minutos para o fim da leitura

Por Tom Dykstra , Jeremy Likness e Jon P Smith

O aplicativo Web da Contoso University demonstra como criar Razor aplicativos Web
pages usando EF Core e o Visual Studio. Para obter informações sobre a série de
tutoriais, consulte o primeiro tutorial.

Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e
compare esse código com o que você criou seguindo o tutorial.

Este tutorial adiciona as funcionalidades de classificação, filtragem e paginação à página


do Aluno.

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. Clique no título de coluna para alternar repetidamente
entre a ordem de classificação ascendente e descendente.
Adicionar classificação
Substitua o código Pages/Students/Index.cshtml.cs pelo código a seguir para adicionar
classificação.

C#

public class IndexModel : PageModel


{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}

public string NameSort { get; set; }


public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }

public IList<Student> Students { get; set; }


public async Task OnGetAsync(string sortOrder)
{
// using System;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

IQueryable<Student> studentsIQ = from s in _context.Students


select s;

switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}

Students = await studentsIQ.AsNoTracking().ToListAsync();


}
}

O código anterior:

Requer a adição using System; .


Adiciona propriedades para conter os parâmetros de classificação.
Altera o nome da propriedade Student para Students .
Substitui o código no método OnGetAsync .

O método OnGetAsync recebe um parâmetro sortOrder da cadeia de caracteres de


consulta na URL. A URL e a cadeia de caracteres de consulta são geradas pelo Auxiliar
de Marca de Âncora.

O sortOrder parâmetro é ou Name Date . Opcionalmente sortOrder , o parâmetro é


seguido para _desc especificar a ordem decrescente. A ordem de classificação padrão é
crescente.

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 crescente por sobrenome é a default da switch instrução. 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 Razor Página para configurar os hiperlinks de

título de coluna com os valores de cadeia de caracteres de consulta apropriados:

C#

NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";


DateSort = sortOrder == "Date" ? "date_desc" : "Date";

O código usa o operador condicional C# ?:. O ?: operador é um operador ternário, ele


leva três operandos. A primeira linha especifica que, quando sortOrder nulo ou vazio,
NameSort é definido como name_desc . Se sortOrder não for nulo ou vazio, NameSort será
definido como uma cadeia de caracteres vazia.

Essas duas instruções permitem que a página defina os hiperlinks de título de coluna da
seguinte maneira:

Ordem de classificação atual Hiperlink do sobrenome Hiperlink de data

Sobrenome ascendente descending ascending

Sobrenome descendente ascending ascending

Data ascendente ascending descending

Data descendente ascending ascending

O método usa o LINQ to Entities para especificar a coluna pela qual classificar. O código
inicializa um IQueryable<Student> antes da instrução switch e modifica-o na instrução
switch:

C#

IQueryable<Student> studentsIQ = from s in _context.Students


select s;

switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}

Students = await studentsIQ.AsNoTracking().ToListAsync();

Quando uma IQueryable consulta é criada ou modificada, 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:

C#

Students = await studentsIQ.AsNoTracking().ToListAsync();

OnGetAsync pode ficar detalhado com um grande número de colunas classificáveis. Para
obter informações sobre uma maneira alternativa de codificar essa funcionalidade,
confira Usar o LINQ dinâmico para simplificar o código na versão MVC desta série de
tutoriais.

Adicionar hiperlinks de título de coluna à página Student


Index
Substitua o código pelo Students/Index.cshtml código a seguir. As alterações são
realçadas.

CSHTML

@page
@model ContosoUniversity.Pages.Students.IndexModel

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

<h2>Students</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.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<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.
Altera o título da página de Índice para Alunos.
Altera Model.Student para Model.Students .
Para verificar se a classificação funciona:

Execute o aplicativo e selecione a guia Alunos.


Clique nos títulos de coluna.

Adicionar filtragem
Para adicionar a filtragem à página Índice de Alunos:

Uma caixa de texto e um botão enviar são adicionados à Razor Página. A caixa de
texto fornece uma cadeia de caracteres de pesquisa no nome ou sobrenome.
O modelo de página é atualizado para usar o valor da caixa de texto.

Atualizar o método OnGetAsync


Substitua o código Students/Index.cshtml.cs pelo seguinte código para adicionar
filtragem:

C#

public class IndexModel : PageModel


{
private readonly SchoolContext _context;

public IndexModel(SchoolContext context)


{
_context = context;
}

public string NameSort { get; set; }


public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }

public IList<Student> Students { get; set; }

public async Task OnGetAsync(string sortOrder, string searchString)


{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

CurrentFilter = searchString;

IQueryable<Student> studentsIQ = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s =>
s.LastName.Contains(searchString)
||
s.FirstMidName.Contains(searchString));
}

switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}

Students = await studentsIQ.AsNoTracking().ToListAsync();


}
}

O código anterior:

Adiciona o parâmetro searchString ao método OnGetAsync e salva o valor do


parâmetro na propriedade CurrentFilter . O valor de cadeia de caracteres de
pesquisa é recebido de uma caixa de texto que é adicionada na próxima seção.
Adiciona 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.

IQueryable vs. IEnumerable


O código 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 as alterações de um método de EF Core DbSet repositório retornem

uma IEnumerable coleção. 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. O SQLite assume como padrão diferenciar
maiúsculas de minúsculas. ToUpper pode ser chamado para fazer com que o teste
diferencie maiúsculas de minúsculas de forma explícita:

C#

Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`

O código anterior garantiria que o filtro não diferenciasse maiúsculas de minúsculas


mesmo que o método Where fosse chamado em um IEnumerable ou fosse executado
no SQLite.

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.

Chamar Contains em um IQueryable é geralmente preferível por motivos de


desempenho. Com IQueryable , a filtragem é feita pelo servidor de banco de dados. Se
um IEnumerable for criado primeiro, todas as linhas precisarão ser retornadas do
servidor de banco de dados.

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.

Para obter mais informações, confira Como usar consulta que não diferencia maiúsculas
de minúsculas com o provedor SQLite .

Atualizar a Razor página


Substitua o código Pages/Students/Index.cshtml para adicionar um botão Pesquisar .

CSHTML

@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}

<h2>Students</h2>

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

<form asp-page="./Index" method="get">


<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString"
value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>

<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model =>
model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<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 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. Se você


estiver usando o SQLite, o filtro não diferenciará maiúsculas de minúsculas apenas
se você tiver implementado o código opcional ToUpper mostrado anteriormente.

Selecione Pesquisar.

Observe que a URL contém a cadeia de caracteres de pesquisa. Por exemplo:

browser-address-bar

https://localhost:5001/Students?SearchString=an

Se a página estiver marcada, o indicador conterá a URL para a página e a cadeia de


caracteres de consulta SearchString . O method="get" na marcação form é o que fez
com que a cadeia de caracteres de consulta fosse gerada.

Atualmente, quando um link de classificação de título de coluna é selecionado, o valor


de filtro da caixa Pesquisa é perdido. O valor de filtro perdido é corrigido na próxima
seção.

Adicionar paginação
Nesta seção, uma classe PaginatedList é criada para dar suporte à paginação. A classe
PaginatedList usa as instruções Skip e Take para filtrar dados no servidor em vez de
recuperar todas as linhas da tabela. A ilustração a seguir mostra os botões de
paginação.

Criar a classe PaginatedList


Na pasta do projeto, crie PaginatedList.cs com o seguinte código:

C#

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

namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }

public PaginatedList(List<T> items, int count, int pageIndex, int


pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);

this.AddRange(items);
}

public bool HasPreviousPage => PageIndex > 1;

public bool HasNextPage => PageIndex < TotalPages;

public static async Task<PaginatedList<T>> CreateAsync(


IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}

O método CreateAsync no código anterior usa o tamanho da página e o número da


página e aplica as instruções Skip e Take ao IQueryable . Quando ToListAsync é
chamado no IQueryable , ele retorna uma Lista que contém somente a página solicitada.
As propriedades HasPreviousPage e HasNextPage são usadas para habilitar ou desabilitar
os botões de paginação Anterior e Próximo.

O método CreateAsync é usado para criar o PaginatedList<T> . Um construtor não pode


criar o objeto PaginatedList<T> ; construtores não podem executar um código
assíncrono.

Adicionar tamanho da página à configuração


Adicione PageSize ao appsettings.json arquivo de configuração:

JSON

{
"PageSize": 3,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Adicionar paginação ao IndexModel


Substitua o código Students/Index.cshtml.cs para adicionar paginação.

C#

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
private readonly IConfiguration Configuration;

public IndexModel(SchoolContext context, IConfiguration


configuration)
{
_context = context;
Configuration = configuration;
}

public string NameSort { get; set; }


public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public PaginatedList<Student> Students { get; set; }

public async Task OnGetAsync(string sortOrder,


string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}

CurrentFilter = searchString;

IQueryable<Student> studentsIQ = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s =>
s.LastName.Contains(searchString)
||
s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}

var pageSize = Configuration.GetValue("PageSize", 4);


Students = await PaginatedList<Student>.CreateAsync(
studentsIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
}
}

O código anterior:
Altera o tipo da propriedade Students de IList<Student> para
PaginatedList<Student> .
Adiciona o índice de página, o sortOrder atual e o currentFilter à assinatura do
método OnGetAsync .
Salva a ordem de classificação na CurrentSort propriedade.
Redefine o índice de página como 1 quando há uma nova cadeia de caracteres de
pesquisa.
Usa a classe PaginatedList para obter entidades de Aluno.
Define pageSize como 3 de Configuração, 4 se a configuração falhar.

Todos os parâmetros que OnGetAsync recebe são nulos quando:

A página é chamada no link Alunos.


O usuário ainda não clicou em um link de paginação ou classificação.

Quando um link de paginação recebe um clique, a variável de índice de páginas contém


o número da página a ser exibido.

A CurrentSort propriedade fornece à Página a Razor ordem de classificação atual. A


ordem de classificação atual precisa ser incluída nos links de paginação para que a
ordem de classificação seja mantida durante a paginação.

A CurrentFilter propriedade fornece à Página a Razor cadeia de caracteres de filtro


atual. O valor CurrentFilter :

Deve ser incluído nos links de paginação para que as configurações de filtro sejam
mantidas durante a paginação.
Deve ser restaurado para a caixa de texto quando a página é exibida novamente.

Se a cadeia de caracteres de pesquisa é alterada durante a paginação, a página é


redefinida como 1. A página precisa ser redefinida como 1, porque o novo filtro pode
resultar na exibição de dados diferentes. Quando um valor de pesquisa é inserido e
Enviar é selecionado:

A cadeia de caracteres de pesquisa foi alterada.


O parâmetro searchString não é nulo.

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 Razor Página.

Os dois pontos de interrogação em pageIndex na chamada PaginatedList.CreateAsync


representam o operador de união de nulo. O operador de união de nulo define um
valor padrão para um tipo que permite valor nulo. A expressão pageIndex ?? 1 retorna
o valor de pageIndex se tiver um valor, caso contrário, retornará 1.

Adicionar links de paginação


Substitua o código Students/Index.cshtml pelo código a seguir. As alterações são
realçadas:

CSHTML

@page
@model ContosoUniversity.Pages.Students.IndexModel

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

<h2>Students</h2>

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

<form asp-page="./Index" method="get">


<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString"
value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>

<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model =>
model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

@{
var prevDisabled = !Model.Students.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Students.HasNextPage ? "disabled" : "";
}

<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @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 :

CSHTML

<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"


asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>

Os botões de paginação são exibidos por auxiliares de marcação:

CSHTML

<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>

Execute o aplicativo e navegue para a página de alunos.

Para verificar se a paginação funciona, clique nos links de paginação em ordens de


classificação diferentes.
Para verificar se a paginação funciona corretamente com a classificação e
filtragem, insira uma cadeia de caracteres de pesquisa e tente fazer a paginação.
Agrupamento
Esta seção cria uma About página que exibe quantos alunos se inscreveram para cada
data de inscrição. A atualização usa o agrupamento e inclui as seguintes etapas:

Crie um modelo de exibição para os dados usados pela About página.


Atualize a About página para usar o modelo de exibição.

Criar o modelo de exibição


Crie uma pasta Models/SchoolViewModels.

Crie SchoolViewModels/EnrollmentDateGroup.cs com o seguinte código:

C#

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }

public int StudentCount { get; set; }


}
}

Criar a Razor página


Crie um Pages/About.cshtml arquivo com o seguinte código:

CSHTML

@page
@model ContosoUniversity.Pages.AboutModel

@{
ViewData["Title"] = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>

@foreach (var item in Model.Students)


{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>

Criar o modelo de página


Atualize o Pages/About.cshtml.cs arquivo com o seguinte código:

C#

using ContosoUniversity.Models.SchoolViewModels;
using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ContosoUniversity.Models;

namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;

public AboutModel(SchoolContext context)


{
_context = context;
}

public IList<EnrollmentDateGroup> Students { get; set; }

public async Task OnGetAsync()


{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};

Students = await data.AsNoTracking().ToListAsync();


}
}
}

A instrução LINQ agrupa as entidades de alunos por data de registro, calcula o número
de entidades em cada grupo e armazena os resultados em uma coleção de objetos de
modelo de exibição EnrollmentDateGroup .

Execute o aplicativo e navegue para a página Sobre. A contagem de alunos para cada
data de registro é exibida em uma tabela.
Próximas etapas
No próximo tutorial, o aplicativo usa migrações para atualizar o modelo de dados.

Tutorial anterior Próximo tutorial


Parte 4, Razor Páginas com EF Core
migrações em ASP.NET Core
Artigo • 28/11/2022 • 12 minutos para o fim da leitura

Por Tom Dykstra , Jon P Smith e Rick Anderson

O aplicativo Web da Contoso University demonstra como criar Razor aplicativos Web
pages usando EF Core e o Visual Studio. Para obter informações sobre a série de
tutoriais, consulte o primeiro tutorial.

Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e
compare esse código com o que você criou seguindo o tutorial.

Este tutorial apresenta o EF Core recurso de migrações para gerenciar alterações de


modelo de dados.

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. Esta série de tutoriais começa configurando o Entity Framework para criar o
banco de dados, caso ele não exista. Cada vez que o modelo de dados é alterado, o
banco de dados precisa ser removido. Na próxima vez em que o aplicativo for
executado, a chamada para EnsureCreated recriará o banco de dados para que
corresponda ao novo modelo de dados. A classe DbInitializer então é executada para
propagar o novo banco de dados.

Essa abordagem para manter o BD em sincronia com o modelo de dados funciona bem
até que o aplicativo precise ser implantado 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 EF Core recurso Migrações resolve esse
problema, permitindo EF Core atualizar o esquema de BD em vez de criar um novo
banco de dados.

Em vez de remover e recriar o banco de dados quando o modelo de dados é alterado,


as migrações atualizam o esquema e retêm os dados existentes.

7 Observação

Limitações do SQLite
Este tutorial usa o recurso de migrações do Entity Framework Core sempre que
possível. As migrações atualizam o esquema de banco de dados para corresponder
às alterações no modelo de dados. No entanto, as migrações só fazem os tipos de
alterações compatíveis com o mecanismo de banco de dados e os recursos de
alteração de esquema do SQLite são limitados. Por exemplo, há suporte para
adicionar uma coluna, mas não há suporte para a remoção de uma coluna. Se uma
migração for criada para remover uma coluna, o comando ef migrations add terá
êxito, mas o comando ef database update falhará.

A solução alternativa para as limitações do SQLite é escrever manualmente o


código das migrações para executar uma recompilação de tabela quando algo na
tabela for alterado. O código entra e usa métodos Up Down para uma migração e
envolve:

Criar uma nova tabela.


Copiar dados da tabela antiga para a nova tabela.
Remover a tabela antiga.
Renomear a nova tabela.

Escrever código específico do banco de dados desse tipo está fora do escopo deste
tutorial. Em vez disso, este tutorial descarta e recria o banco de dados sempre que
uma tentativa de aplicar uma migração falha. Para saber mais, consulte os recursos
a seguir:

Limitações do provedor de banco de dados SQLite EF Core


Personalizar o código de migração
Propagação de dados
Instrução SQLite ALTER TABLE

Remover o banco de dados


Visual Studio

Use o SSOX (Pesquisador de Objetos do SQL Server) para excluir o banco de


dados ou execute o seguinte comando no PMC (Console do Gerenciador de
Pacotes):

PowerShell

Drop-Database
Criar uma migração inicial
Visual Studio

Executar os seguintes comandos no PMC:

PowerShell

Add-Migration InitialCreate
Update-Database

Remover EnsureCreated
Esta série de tutoriais começou usando o EnsureCreated. O EnsureCreated não cria uma
tabela de histórico de migrações, portanto, não pode ser usada com migrações. Ele foi
projetado para teste ou criação rápida de protótipos em que o banco de dados é
removido e recriado com frequência.

Deste ponto em diante, os tutoriais usarão as migrações.

Em Program.cs , exclua a seguinte linha:

C#

context.Database.EnsureCreated();

Execute o aplicativo e verifique se o banco de dados é propagado.

Métodos Para Cima e Para Baixo


O EF Core migrations add código gerado pelo comando para criar o banco de dados.
Esse código de migrações está no Migrations\<timestamp>_InitialCreate.cs arquivo. O
método Up da classe InitialCreate cria as tabelas de banco de dados que
correspondem aos conjuntos de entidades do modelo de dados. O método Down exclui-
os, conforme mostrado no seguinte exemplo:

C#
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;

namespace ContosoUniversity.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Course",
columns: table => new
{
CourseID = table.Column<int>(nullable: false),
Title = table.Column<string>(nullable: true),
Credits = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Course", x => x.CourseID);
});

migrationBuilder.CreateTable(
name: "Student",
columns: table => new
{
ID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
LastName = table.Column<string>(nullable: true),
FirstMidName = table.Column<string>(nullable: true),
EnrollmentDate = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Student", x => x.ID);
});

migrationBuilder.CreateTable(
name: "Enrollment",
columns: table => new
{
EnrollmentID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
CourseID = table.Column<int>(nullable: false),
StudentID = table.Column<int>(nullable: false),
Grade = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Enrollment", x => x.EnrollmentID);
table.ForeignKey(
name: "FK_Enrollment_Course_CourseID",
column: x => x.CourseID,
principalTable: "Course",
principalColumn: "CourseID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Enrollment_Student_StudentID",
column: x => x.StudentID,
principalTable: "Student",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
});

migrationBuilder.CreateIndex(
name: "IX_Enrollment_CourseID",
table: "Enrollment",
column: "CourseID");

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

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(
name: "Enrollment");

migrationBuilder.DropTable(
name: "Course");

migrationBuilder.DropTable(
name: "Student");
}
}
}

O código anterior refere-se à migração inicial. O código:

Foi gerado pelo comando migrations add InitialCreate .


É executado pelo comando database update .
Cria um banco de dados para o modelo de dados especificado pela classe de
contexto do banco de dados.

O parâmetro de nome de 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".
A tabela de histórico de migrações
Use a ferramenta SSOX ou SQLite para inspecionar o banco de dados.
Observe a adição de uma tabela __EFMigrationsHistory . A tabela
__EFMigrationsHistory controla quais migrações foram aplicadas ao banco de
dados.
Exibir os dados na tabela __EFMigrationsHistory . Mostra uma linha para a primeira
migração.

O instantâneo do modelo de dados


As migrações criam um instantâneo do modelo de dados atual em
Migrations/SchoolContextModelSnapshot.cs . Quando uma migração é adicionada, o EF

determina o que foi alterado comparando o modelo de dados atual com o arquivo de
instantâneo.

Como o arquivo de instantâneo rastreia o estado do modelo de dados, uma migração


não pode ser excluída excluindo o <timestamp>_<migrationname>.cs arquivo. Para fazer
backup da migração mais recente, use o migrations remove comando. migrations
remove exclui a migração e garante que o instantâneo seja redefinido corretamente. Para
obter mais informações, confira as migrações de ef dotnet removidas.

Consulte Redefinir todas as migrações para remover todas as migrações.

Aplicando migrações na produção


Recomendamos que os aplicativos de produção não chamem Database.Migrate na
inicialização do aplicativo. Migrate não deve ser chamado de um aplicativo implantado
em um farm de servidores. Se o aplicativo for escalado horizontalmente para várias
instâncias de servidor, será difícil garantir que as atualizações do esquema de banco de
dados não ocorram de vários servidores ou que estejam em conflito com o acesso de
leitura/gravação.

A migração de banco de dados deve ser feita como parte da implantação e de maneira
controlada. Abordagens de migração de banco de dados de produção incluem:

Uso de migrações para criar scripts SQL e uso dos scripts SQL na implantação.
Execução de dotnet ef database update em um ambiente controlado.

Solução de problemas
Se o aplicativo usar SQL Server LocalDB e exibir a seguinte exceção:

text

SqlException: Cannot open database "ContosoUniversity" requested by the


login.
The login failed.
Login failed for user 'user name'.

A solução pode ser executar dotnet ef database update em um prompt de comando.

Recursos adicionais
EF Core CLI.
Comandos da CLI de migrações dotnet ef
Console do Gerenciador de Pacotes (Visual Studio)

Próximas etapas
O próximo tutorial cria o modelo de dados adicionando propriedades da entidade e
novas entidades.

Tutorial anterior Próximo tutorial


Parte 5, Razor Páginas com EF Core em
ASP.NET Core – Modelo de Dados
Artigo • 03/12/2022 • 84 minutos para o fim da leitura

Por Tom Dykstra , Jeremy Likness e Jon P Smith

O aplicativo Web da Contoso University demonstra como criar Razor aplicativos Web de
páginas usando EF Core e o Visual Studio. Para obter informações sobre a série de
tutoriais, consulte o primeiro tutorial.

Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e
compare esse código com o que você criou seguindo o 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.

O modelo de dados concluído é mostrado na seguinte ilustração:


O diagrama de banco de dados a seguir foi feito com o Dataedo :
Para criar um diagrama de banco de dados com o Dataedo:

Implantar o aplicativo no Azure


Baixe e instale o Dataedo no computador.
Siga as instruções Gerar documentação para o Banco de Dados SQL do Azure em
5 minutos

No diagrama dataedo anterior, a CourseInstructor tabela de junção é criada pelo Entity


Framework. Para obter mais informações, consulte Muitos para muitos

A entidade Student
Substitua o código em Models/Student.cs pelo seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than
50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}

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


}
}

O código anterior adiciona uma propriedade FullName e adiciona os seguintes atributos


às propriedades existentes:

[DataType]
[DisplayFormat]
[StringLength]
[Column]
[Required]
[Display]

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, assim, ele apenas tem
um acessador get. Nenhuma coluna FullName é criada no banco de dados.

O atributo DataType
C#

[DataType(DataType.Date)]
Para as datas de registro do aluno, todas as páginas atualmente exibem a hora do dia
junto com a data, embora apenas a data seja relevante. 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 página que mostra os dados.

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 Data, Hora,
Número de Telefone, Conversor de Moedas, EmailAddress etc. O DataType atributo
também pode permitir que o aplicativo forneça automaticamente recursos específicos
do 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 HTML 5 data- (pronunciados como data dash). Os
atributos DataType não fornecem validação.

O atributo DisplayFormat
C#

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


true)]

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 e
validação de entrada do lado do cliente.
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 de


Entrada>.

O atributo StringLength
C#

[StringLength(50, ErrorMessage = "First name cannot be longer than 50


characters.")]

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 código mostrado limita os
nomes a, no máximo, 50 caracteres. Um exemplo que define o comprimento mínimo da
cadeia de caracteres é mostrado posteriormente.

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.

O atributo StringLength não impede que um usuário insira um espaço em branco em


um nome. O atributo RegularExpression pode ser 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:

C#

[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]

Visual Studio

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) . Quando uma migração é criada e aplicada
posteriormente neste tutorial, os campos de nome se tornam nvarchar(50) como
resultado dos atributos de comprimento da cadeia de caracteres.

O atributo Column
C#

[Column("FirstName")]
public string FirstMidName { get; set; }

Os atributos podem controlar como as classes e propriedades são mapeadas para o


banco de dados. No modelo Student , o atributo Column é usado para mapear o nome
da propriedade FirstMidName para "FirstName" no banco de dados.

Quando o banco de dados é 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.

Com o atributo [Column] , Student.FirstMidName no modelo de dados é 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. Essa discrepância será resolvida adicionando uma
migração posteriormente neste tutorial.

O atributo Required
C#

[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
(por exemplo, DateTime , int e double ). Tipos que não podem ser nulos são tratados
automaticamente como campos obrigatórios.

O atributo Required precisa ser usado com MinimumLength para que MinimumLength seja
imposto.

C#

[Display(Name = "Last Name")]


[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

MinimumLength e Required permitem que o espaço em branco atenda à validação. Use o


atributo RegularExpression para obter controle total sobre a cadeia de caracteres.

O atributo Display
C#

[Display(Name = "Last Name")]

O Display atributo especifica que a legenda das caixas de texto deve ser "Nome",
"Sobrenome", "Nome Completo" e "Data de Registro". As legendas padrão não tinham
espaço dividindo as palavras, por exemplo, "Sobrenome".

Criar uma migração


Execute o aplicativo e acesse a página Alunos. Uma exceção é gerada. O atributo
[Column] faz com que o EF Espere encontrar uma coluna chamada FirstName , mas o
nome da coluna no banco de dados ainda é FirstMidName .

Visual Studio

A mensagem de erro é semelhante ao exemplo a seguir:

SqlException: Invalid column name 'FirstName'.


There are pending model changes
Pending model changes are detected in the following:

SchoolContext

No PMC, insira os seguintes comandos para criar uma nova migração e


atualizar o banco de dados:

PowerShell

Add-Migration ColumnFirstName
Update-Database

O primeiro desses comandos gera a seguinte mensagem de aviso:

text

An operation was scaffolded that may result in the loss of data.


Please review the migration for accuracy.

O aviso é gerado porque os campos de nome agora estão limitados a 50


caracteres. Se um nome no banco de dados tiver mais de 50 caracteres, o 51º
caractere até o último caractere serão perdidos.

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 .

Execute o aplicativo e acesse a página Alunos.


Observe que os horários não são inseridos nem exibidos juntamente com datas.
Selecione Criar Novo e tente inserir um nome com mais de 50 caracteres.

7 Observação

Nas seções a seguir, a criação do aplicativo em alguns estágios gera erros do


compilador. As instruções especificam quando compilar o aplicativo.

A entidade Instructor
Crie Models/Instructor.cs com o seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }

[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }

[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }

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

[Display(Name = "Full Name")]


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

public ICollection<Course> Courses { get; set; }


public OfficeAssignment OfficeAssignment { get; set; }
}
}

Vários atributos podem estar em uma linha. Os atributos HireDate podem ser escritos
da seguinte maneira:

C#

[DataType(DataType.Date),Display(Name = "Hire
Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]

Propriedades de navegação
As propriedades Courses e OfficeAssignment são propriedades de navegação.

Um instrutor pode ministrar qualquer quantidade de cursos e, portanto, Courses é


definido como uma coleção.
C#

public ICollection<Course> Courses { get; set; }

Um instrutor pode ter no máximo um escritório, portanto, a propriedade


OfficeAssignment mantém uma única entidade OfficeAssignment . OfficeAssignment

será nulo se nenhum escritório for atribuído.

C#

public OfficeAssignment OfficeAssignment { get; set; }

A entidade OfficeAssignment

Crie Models/OfficeAssignment.cs com o seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }

public Instructor Instructor { get; set; }


}
}

O atributo Key
O [Key] atributo é usado para identificar uma propriedade como a chave primária (PK)
quando o nome da propriedade é algo diferente classnameID de 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 . Uma relação de um para zero ou um ocorre quando um PK em uma tabela

é um PK e um FK em outra tabela.

EF Core não é possível reconhecer InstructorID automaticamente como o PK porque


OfficeAssignment InstructorID não segue a convenção de nomenclatura ID ou
classnameID. Portanto, o atributo Key é usado para identificar InstructorID como a PK:

C#

[Key]
public int InstructorID { get; set; }

Por padrão, EF Core trata a chave como não gerada pelo banco de dados porque a
coluna é para uma relação de identificação. Para obter mais informações, consulte
Chaves EF.

A propriedade de navegação Instructor


A propriedade de navegação Instructor.OfficeAssignment pode ser nula porque pode
não haver uma linha OfficeAssignment para um determinado instrutor. Um instrutor
pode não ter uma atribuição de escritório.

A propriedade de navegação OfficeAssignment.Instructor sempre terá uma entidade


de instrutor porque o tipo InstructorID de chave estrangeira é int , um tipo de valor
não anulável. 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.

A entidade Course
Atualize Models/Course.cs com o seguinte código:

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

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Title { get; set; }

[Range(0, 5)]
public int Credits { get; set; }

public int DepartmentID { get; set; }

public Department Department { get; set; }


public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<Instructor> Instructors { 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 .

EF Core não requer uma propriedade de chave estrangeira para um modelo de dados
quando o modelo tem uma propriedade de navegação para uma entidade relacionada.
EF Core cria automaticamente FKs no banco de dados onde quer que sejam necessários.
EF Core cria propriedades de sombra para FKs criados automaticamente. Porém, incluir
explicitamente 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 DepartmentID
FK não está incluída. Quando uma entidade de curso é buscada para editar:

A Department propriedade será null se ela não for carregada explicitamente.


Para atualizar a entidade de curso, a entidade Department primeiro deve ser
buscada.

Quando a propriedade de FK DepartmentID está incluída no modelo de dados, não é


necessário buscar a entidade Department antes de uma atualização.

O atributo DatabaseGenerated
O atributo [DatabaseGenerated(DatabaseGeneratedOption.None)] especifica que a PK é
fornecida pelo aplicativo em vez de ser gerada pelo banco de dados.

C#

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Por padrão, EF Core pressupõe que os valores de PK sejam gerados pelo banco de
dados. O banco de dados gerado costuma ser 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 banco de dados 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 .

C#

public int DepartmentID { get; set; }


public Department Department { get; set; }

Um curso pode ter qualquer quantidade de estudantes inscritos; portanto, a


propriedade de navegação Enrollments é uma coleção:

C#

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

Um curso pode ser ministrado por vários instrutores; portanto, a propriedade de


navegação Instructors é uma coleção:
C#

public ICollection<Instructor> Instructors { get; set; }

A entidade Department
Crie Models/Department.cs com o seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

O atributo Column
Anteriormente, o atributo Column foi usado para alterar o mapeamento de nome de
coluna. No código da entidade Department , o atributo Column é usado para alterar o
mapeamento de tipo de dados SQL. A coluna Budget é definida usando o tipo de
dinheiro do SQL Server no banco de dados:
C#

[Column(TypeName="money")]
public decimal Budget { get; set; }

Em geral, o mapeamento de coluna não é necessário. EF Coreescolhe o tipo de dados


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 :

C#

public int? InstructorID { get; set; }


public Instructor Administrator { get; set; }

O ? código anterior especifica que a propriedade é anulável.

Um departamento pode ter vários cursos e, portanto, há uma propriedade de


navegação Courses:

C#

public ICollection<Course> Courses { get; set; }

Por convenção, EF Core habilita a exclusão em cascata para FKs não anuláveis e para
relações muitos para muitos. Esse comportamento padrão pode resultar em regras
circulares de exclusão em cascata. As regras de exclusão em cascata circular causam
uma exceção quando uma migração é adicionada.

Por exemplo, se a Department.InstructorID propriedade fosse definida como não


anulável, EF Core configuraria uma regra de exclusão em cascata. Nesse caso, o
departamento seria excluído quando o instrutor atribuído como seu administrador fosse
excluído. Nesse cenário, uma regra restrita fará mais sentido. A API fluente a seguir
definiria uma regra de restrição e desabilitaria a exclusão em cascata.

C#

modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)

As propriedades de navegação e chave estrangeira


registro
Um registro se refere a um curso feito por um aluno.

Atualize Models/Enrollment.cs com o seguinte código:

C#

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

As propriedades de navegação e de FK refletem as seguintes relações:

Um registro destina-se a um curso e, portanto, há uma propriedade de FK CourseID e


uma propriedade de navegação Course :

C#

public int CourseID { get; set; }


public Course Course { get; set; }

Um registro destina-se a um aluno e, portanto, há uma propriedade de FK StudentID e


uma propriedade de navegação Student :

C#

public int StudentID { get; set; }


public Student Student { get; set; }

Relações muitos para muitos


Há uma relação muitos para muitos entre as entidades Student e Course . A Enrollment
entidade funciona como uma tabela de junção muitos para muitos com conteúdo no
banco de dados. Com o conteúdo significa que a Enrollment tabela contém dados
adicionais além de FKs para as tabelas unidas. Enrollment Na entidade, os dados
adicionais além de FKs são o PK e Grade .

A ilustração a seguir mostra a aparência dessas relações em um diagrama de entidades.


(Este diagrama foi gerado usando o EF Power Tools para EF 6.x. A criação do diagrama
não faz parte do tutorial.)
Cada linha de relação tem um 1 em uma extremidade e um asterisco (*) na outra,
indicando uma relação um para muitos.

Se a Enrollment tabela não incluísse informações de nota, ela só precisaria conter os


dois FKs CourseID e StudentID . Uma tabela de junção muitos para muitos sem
conteúdo é às vezes chamada de PJT (uma tabela de junção pura).

As Instructor entidades e Course as entidades têm uma relação muitos para muitos
usando um PJT.

Atualizar o contexto de banco de dados


Atualize Data/SchoolContext.cs com o seguinte código:

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable(nameof(Course))
.HasMany(c => c.Instructors)
.WithMany(i => i.Courses);
modelBuilder.Entity<Student>().ToTable(nameof(Student));
modelBuilder.Entity<Instructor>().ToTable(nameof(Instructor));
}
}
}

O código anterior adiciona as novas entidades e configura a relação muitos para muitos
entre entidades Instructor e Course entidades.

Alternativa de API fluente para atributos


O OnModelCreating método no código anterior usa a API fluente para configurar EF Core
o comportamento. A API é chamada "fluente" porque geralmente é usada pelo
encadeamento de uma série de chamadas de método em uma única instrução. O
seguinte código é um exemplo da API fluente:

C#

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}

Neste tutorial, a API fluente é usada apenas para o mapeamento do banco de dados
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 só podem ser feitas com a API fluente, por
exemplo, especificando um PK composto. 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 ).


EF Core somente configuração (por exemplo, HasKey ).
Validação e EF Core configuração (por exemplo, [StringLength(50)] ).

Para obter mais informações sobre atributos vs. API fluente, consulte Métodos de
configuração.

Propagar o banco de dados


Atualize o código em Data/DbInitializer.cs :

C#

using ContosoUniversity.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}

var alexander = new Student


{
FirstMidName = "Carson",
LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2016-09-01")
};

var alonso = new Student


{
FirstMidName = "Meredith",
LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01")
};

var anand = new Student


{
FirstMidName = "Arturo",
LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01")
};

var barzdukas = new Student


{
FirstMidName = "Gytis",
LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01")
};

var li = new Student


{
FirstMidName = "Yan",
LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01")
};

var justice = new Student


{
FirstMidName = "Peggy",
LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01")
};

var norman = new Student


{
FirstMidName = "Laura",
LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01")
};

var olivetto = new Student


{
FirstMidName = "Nino",
LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01")
};
var students = new Student[]
{
alexander,
alonso,
anand,
barzdukas,
li,
justice,
norman,
olivetto
};

context.AddRange(students);

var abercrombie = new Instructor


{
FirstMidName = "Kim",
LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11")
};

var fakhouri = new Instructor


{
FirstMidName = "Fadi",
LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06")
};

var harui = new Instructor


{
FirstMidName = "Roger",
LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01")
};

var kapoor = new Instructor


{
FirstMidName = "Candace",
LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15")
};

var zheng = new Instructor


{
FirstMidName = "Roger",
LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12")
};

var instructors = new Instructor[]


{
abercrombie,
fakhouri,
harui,
kapoor,
zheng
};

context.AddRange(instructors);

var officeAssignments = new OfficeAssignment[]


{
new OfficeAssignment {
Instructor = fakhouri,
Location = "Smith 17" },
new OfficeAssignment {
Instructor = harui,
Location = "Gowan 27" },
new OfficeAssignment {
Instructor = kapoor,
Location = "Thompson 304" }
};

context.AddRange(officeAssignments);

var english = new Department


{
Name = "English",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = abercrombie
};

var mathematics = new Department


{
Name = "Mathematics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = fakhouri
};

var engineering = new Department


{
Name = "Engineering",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = harui
};

var economics = new Department


{
Name = "Economics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = kapoor
};

var departments = new Department[]


{
english,
mathematics,
engineering,
economics
};

context.AddRange(departments);

var chemistry = new Course


{
CourseID = 1050,
Title = "Chemistry",
Credits = 3,
Department = engineering,
Instructors = new List<Instructor> { kapoor, harui }
};

var microeconomics = new Course


{
CourseID = 4022,
Title = "Microeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};

var macroeconmics = new Course


{
CourseID = 4041,
Title = "Macroeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};

var calculus = new Course


{
CourseID = 1045,
Title = "Calculus",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { fakhouri }
};

var trigonometry = new Course


{
CourseID = 3141,
Title = "Trigonometry",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { harui }
};

var composition = new Course


{
CourseID = 2021,
Title = "Composition",
Credits = 3,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};

var literature = new Course


{
CourseID = 2042,
Title = "Literature",
Credits = 4,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};

var courses = new Course[]


{
chemistry,
microeconomics,
macroeconmics,
calculus,
trigonometry,
composition,
literature
};

context.AddRange(courses);

var enrollments = new Enrollment[]


{
new Enrollment {
Student = alexander,
Course = chemistry,
Grade = Grade.A
},
new Enrollment {
Student = alexander,
Course = microeconomics,
Grade = Grade.C
},
new Enrollment {
Student = alexander,
Course = macroeconmics,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = calculus,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = trigonometry,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = anand,
Course = chemistry
},
new Enrollment {
Student = anand,
Course = microeconomics,
Grade = Grade.B
},
new Enrollment {
Student = barzdukas,
Course = chemistry,
Grade = Grade.B
},
new Enrollment {
Student = li,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = justice,
Course = literature,
Grade = Grade.B
}
};

context.AddRange(enrollments);
context.SaveChanges();
}
}
}

O código anterior fornece dados de semente para as novas entidades. A maioria desse
código cria novos objetos de entidade e carrega dados de exemplo. Os dados de
exemplo são usados para teste.

Aplicar a migração ou remover e recriar


Com o banco de dados existente, há duas abordagens para alterar o banco de dados:

Solte e crie novamente o banco de dados. Escolha esta seção ao usar SQLite.
Aplique a migração ao banco de dados existente. As instruções nesta seção
funcionam apenas para SQL Server, não para SQLite.
Qualquer opção funciona para o SQL Server. Embora o método apply-migration seja
mais complexo e demorado, é a abordagem preferencial para ambientes de produção
do mundo real.

Remover e recriar o banco de dados


Para forçar EF Core a criação de um novo banco de dados, solte e atualize o banco de
dados:

Visual Studio

Exclua a pasta Migração.


No PMC ( Console do Gerenciador de Pacotes ), execute os seguintes
comandos:

PowerShell

Drop-Database
Add-Migration InitialCreate
Update-Database

Execute o aplicativo. A execução do aplicativo executa o método


DbInitializer.Initialize . O DbInitializer.Initialize preenche o novo banco de

dados.

Visual Studio

Abra o banco de dados no SSOX:

Se o SSOX for aberto anteriormente, clique no botão Atualizar.


Expanda o nó Tabelas. As tabelas criadas são exibidas.

Próximas etapas
Os próximos dois tutoriais mostram como ler e atualizar dados relacionados.

Tutorial anterior Próximo tutorial


Parte 6, Razor Páginas com EF Core em
ASP.NET Core – Ler Dados Relacionados
Artigo • 28/11/2022 • 42 minutos para o fim da leitura

Por Tom Dykstra , Jon P Smith e Rick Anderson

O aplicativo Web da Contoso University demonstra como criar Razor aplicativos Web de
páginas usando EF Core e o Visual Studio. Para obter informações sobre a série de
tutoriais, consulte o primeiro tutorial.

Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e
compare esse código com o que você criou seguindo o tutorial.

Este tutorial mostra como ler e exibir dados relacionados. Dados relacionados são dados
que EF Core são carregados em propriedades de navegação.

As seguintes ilustrações mostram as páginas concluídas para este tutorial:


Carregamento adiantado, explícito e lento
Há várias maneiras de EF Core carregar dados relacionados nas propriedades de
navegação de uma entidade:

Carregamento ansioso. O carregamento adiantado é quando uma consulta para


um tipo de entidade também carrega entidades relacionadas. Quando uma
entidade é lida, seus dados relacionados são recuperados. Normalmente, isso
resulta em uma única consulta de junção que recupera todos os dados
necessários. EF Core emitirá várias consultas para alguns tipos de carregamento
ansioso. A emissão de várias consultas pode ser mais eficiente do que uma
consulta única grande. O carregamento adiantado é especificado com os métodos
Include e ThenInclude.

O carregamento adiantado envia várias consultas quando a navegação de coleção


é incluída:
Uma consulta para a consulta principal
Uma consulta para cada "borda" de coleção na árvore de carregamento.

Consultas separadas com Load : Os dados podem ser recuperados em consultas


separadas e EF Core "corrige" as propriedades de navegação. "Correções" significa
que EF Core preenche automaticamente as propriedades de navegação. A
separação de consultas com Load é mais parecida com o carregamento explícito
do que com o carregamento adiantado.

Nota:EF Core corrige automaticamente as propriedades de navegação para


quaisquer outras entidades que foram carregadas anteriormente na instância de
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 banco de dados. 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. 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 banco de
dados sempre que uma propriedade de navegação é acessada pela primeira vez. O
carregamento lento pode prejudicar o desempenho, por exemplo, quando os
desenvolvedores usam consultas N+1 . As consultas N+1 carregam um pai e
enumeram por meio de filhos.

Criar páginas do Curso


A entidade Course inclui uma propriedade de navegação que contém a entidade
Department relacionada.
Para exibir o nome do departamento atribuído para um curso:

Carregue a entidade relacionada Department na propriedade de navegação


Course.Department .
Obtenha o nome da propriedade Department da entidade Name .

Aplicar scaffold às páginas do curso

Visual Studio

Siga as instruções em páginas do aluno do Scaffold com as seguintes


exceções:
Crie uma pasta Pages/Courses.
Use Course para a classe de modelo.
Use a classe de contexto existente, em vez de criar uma nova.
Abra Pages/Courses/Index.cshtml.cs e examine o OnGetAsync método. O
mecanismo de scaffolding especificou o carregamento adiantado para a
propriedade de navegação Department . O método Include especifica o
carregamento adiantado.

Execute o aplicativo e selecione o link Cursos. A coluna de departamento exibe a


DepartmentID , que não é útil.

Exibir o nome do departamento


Atualize Pages/Courses/Index.cshtml.cs com o seguinte código:

C#

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

namespace ContosoUniversity.Pages.Courses
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public IndexModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public IList<Course> Courses { get; set; }

public async Task OnGetAsync()


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

O código anterior altera a propriedade Course para Courses e adiciona AsNoTracking .


AsNoTracking melhora o desempenho porque as entidades retornadas não são

controladas. As entidades não precisam ser controladas porque não são atualizadas no
contexto atual.
Atualize Pages/Courses/Index.cshtml com o seguinte código.

CSHTML

@page
@model ContosoUniversity.Pages.Courses.IndexModel

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

<h1>Courses</h1>

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

As seguintes alterações foram feitas na biblioteca gerada por código em scaffolding:

O nome da propriedade Course foi alterado para Courses .

Adicionou uma coluna Número que mostra o valor da propriedade CourseID . Por
padrão, as chaves primárias não são geradas por scaffolding porque normalmente
não têm sentido para os usuários finais. No entanto, nesse caso, a chave primária é
significativa.

Alterou a coluna Departamento para que ela exiba o nome de departamento. O


código exibe a propriedade Name da entidade Department que é carregada na
propriedade de navegação Department :

HTML

@Html.DisplayFor(modelItem => item.Department.Name)

Execute o aplicativo e selecione a guia Cursos para ver a lista com nomes de
departamentos.

Carregando dados relacionados com Select


O método OnGetAsync carrega dados relacionados com o método Include . O método
Select é uma alternativa que carrega apenas os dados relacionados necessários. Para
itens individuais, como o Department.Name que 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 :

C#

public IList<CourseViewModel> CourseVM { get; set; }

public async Task OnGetAsync()


{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}

O código anterior não retorna nenhum tipo de entidade, portanto, nenhum


acompanhamento é feito. Para obter mais informações sobre o acompanhamento do EF,
consulte Tracking vs. No-Tracking Queries.

CourseViewModel :

C#

public class CourseViewModel


{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}

Consulte IndexSelectModel para ver as páginas completas Razor .

Criar as páginas de Instrutor


Esta seção aplica scaffold a páginas do Instrutor e adiciona Cursos e Inscrições
relacionados à página Índice de Instrutores.
Essa página lê e exibe dados relacionados das seguintes maneiras:

A lista de instrutores exibe dados relacionados da entidade OfficeAssignment


(Office na imagem anterior). As entidades Instructor e OfficeAssignment estão
em uma relação um para zero ou um. O carregamento adiantado é usado para as
entidades OfficeAssignment . O carregamento adiantado costuma ser mais
eficiente quando os dados relacionados precisam ser exibidos. Nesse caso, as
atribuições de escritório para os instrutores são exibidas.
Quando o usuário seleciona um instrutor, as entidades Course relacionadas são
exibidas. As entidades Instructor e Course estão em uma relação muitos para
muitos. O carregamento adiantado é usado para entidades Course e suas
entidades Department relacionadas. Nesse caso, consultas separadas podem ser
mais eficientes porque somente os cursos para o instrutor selecionado são
necessários. Este exemplo mostra como usar o carregamento adiantado para
propriedades de navegação em entidades que estão nas propriedades de
navegação.
Quando o usuário seleciona um curso, dados relacionados da entidade
Enrollments são exibidos. Na imagem anterior, o nome do aluno e a nota são

exibidos. As entidades Course e Enrollment estão em uma relação um-para-


muitos.

Criar um modelo de exibição


A página Instrutores mostra dados de três tabelas diferentes. É necessário um modelo
de exibição que inclui três propriedades que representam as três tabelas.

Crie Models/SchoolViewModels/InstructorIndexData.cs com o seguinte código:

C#

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

Aplicar scaffold às páginas do Instrutor

Visual Studio

Siga as instruções em Aplicar scaffold às páginas do aluno com as seguintes


exceções:
Crie uma pasta Pages/Instructors.
Use Instructor para a classe de modelo.
Use a classe de contexto existente, em vez de criar uma nova.
Execute o aplicativo e navegue até a página Instrutores.

Atualize Pages/Instructors/Index.cshtml.cs com o seguinte código:

C#

using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public IndexModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public InstructorIndexData InstructorData { get; set; }


public int InstructorID { get; set; }
public int CourseID { get; set; }

public async Task OnGetAsync(int? id, int? courseID)


{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}

if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await
_context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}
}
}
}

O método OnGetAsync aceita dados de rota opcionais para a ID do instrutor


selecionado.

Examine a consulta no Pages/Instructors/Index.cshtml.cs arquivo:

C#

InstructorData = new InstructorIndexData();


InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();

O código especifica o carregamento adiantado para as seguintes propriedades de


navegação:

Instructor.OfficeAssignment
Instructor.Courses

Course.Department

O código a seguir é executado quando um instrutor é selecionado, ou seja, id != null .

C#

if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}

O instrutor selecionado é recuperado da lista de instrutores no modelo de exibição. A


propriedade do modelo de Courses exibição é carregada com as Course entidades da
propriedade de navegação do Courses instrutor selecionado.

O método Where retorna uma coleção. Nesse caso, o filtro seleciona uma única
entidade, portanto, o Single método é chamado para converter a coleção em uma
única Instructor entidade. A Instructor entidade fornece acesso à Course
propriedade de navegação.

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 se a coleção estiver
vazia. Para essa consulta, null no padrão retornado.

O seguinte código popula a propriedade Enrollments do modelo de exibição quando


um curso é selecionado:

C#

if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await _context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}

Atualizar a página Índice de instrutores


Atualize Pages/Instructors/Index.cshtml com o seguinte código.

CSHTML

@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel

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

<h2>Instructors</h2>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.InstructorData.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "table-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.Courses)
{
@course.CourseID @: @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>

@if (Model.InstructorData.Courses != null)


{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>

@foreach (var item in Model.InstructorData.Courses)


{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-route-
courseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}

</table>
}

@if (Model.InstructorData.Enrollments != null)


{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.InstructorData.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
O código anterior faz as seguintes alterações:

Atualizações a page diretiva para @page "{id:int?}" . "{id:int?}" é um modelo de


rota. O modelo de rota altera cadeias de caracteres de consulta inteiro na URL para
rotear dados. Por exemplo, clicar no link Selecionar de um o instrutor apenas com
a diretiva @page produz uma URL semelhante à seguinte:

https://localhost:5001/Instructors?id=2

Quando a diretiva de página é @page "{id:int?}" , a URL é:


https://localhost:5001/Instructors/2

Adiciona uma coluna Escritório que exibirá item.OfficeAssignment.Location


somente se item.OfficeAssignment não for nulo. Como essa é uma relação um
para zero ou um, pode não haver uma entidade OfficeAssignment relacionada.

HTML

@if (item.OfficeAssignment != null)


{
@item.OfficeAssignment.Location
}

Adiciona uma coluna Cursos que exibe os cursos ministrados por cada instrutor.
Confira a transição de linha explícita para saber mais sobre essa sintaxe razor.

Adiciona um código que adiciona dinamicamente class="table-success" ao


elemento tr do instrutor e do curso selecionados. Isso define uma cor da tela de
fundo para a linha selecionada usando uma classe Bootstrap.

HTML

string selectedRow = "";


if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">

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

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

Adiciona uma tabela de cursos para o Instrutor selecionado.

Adiciona uma tabela de inscrições de alunos para o curso selecionado.

Execute o aplicativo e selecione a guia Instrutores . A página exibe o Location (office)


da entidade relacionada OfficeAssignment . Se OfficeAssignment for nulo, uma célula de
tabela vazia será exibida.

Clique no link Selecionar para um instrutor. As alterações de estilo de linha e os cursos


atribuídos a esse instrutor são exibidos.

Selecione um curso para ver a lista de alunos registrados e suas notas.


Próximas etapas
O próximo tutorial mostra como atualizar os dados relacionados.

Tutorial anterior Próximo tutorial


Parte 7, Razor Páginas com EF Core em
ASP.NET Core – Atualizar dados
relacionados
Artigo • 04/01/2023 • 51 minutos para o fim da leitura

Por Tom Dykstra , Jon P Smith e Rick Anderson

O aplicativo Web da Contoso University demonstra como criar Razor aplicativos Web de
páginas usando EF Core e o Visual Studio. Para obter informações sobre a série de
tutoriais, consulte o primeiro tutorial.

Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e
compare esse código com o que você criou seguindo o tutorial.

O tutorial mostra como atualizar os dados relacionados. As ilustrações a seguir mostram


algumas das páginas concluídas.
Atualizar o curso criar e editar páginas
O código scaffolded para as páginas Criar e Editar cursos tem uma lista suspensa do
Departamento que mostra DepartmentID , um int . A lista suspensa deve mostrar o
nome do Departamento, portanto, ambas as páginas precisam de uma lista de nomes
de departamento. Para fornecer essa lista, use uma classe base para as páginas Criar e
Editar.

Criar uma classe base para Criar e Editar o Curso


Crie um Pages/Courses/DepartmentNamePageModel.cs arquivo com o seguinte código:

C#

using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
public class DepartmentNamePageModel : PageModel
{
public SelectList DepartmentNameSL { get; set; }

public void PopulateDepartmentsDropDownList(SchoolContext _context,


object selectedDepartment = null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name // Sort by name.
select d;

DepartmentNameSL = new
SelectList(departmentsQuery.AsNoTracking(),
nameof(Department.DepartmentID),
nameof(Department.Name),
selectedDepartment);
}
}
}

O código anterior cria uma SelectList para conter a lista de nomes de departamento. 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 .

Atualizar o modelo de página Criar do Curso


Um Curso é atribuído a um Departamento. A classe base para as páginas Criar e Editar
fornece um SelectList para selecionar o departamento. A lista suspensa que usa
SelectList define a propriedade de FK (chave estrangeira) Course.DepartmentID . EF
Core usa o Course.DepartmentID FK para carregar a propriedade de Department
navegação.

Atualize Pages/Courses/Create.cshtml.cs com o seguinte código:

C#
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
public class CreateModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public CreateModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public IActionResult OnGet()


{
PopulateDepartmentsDropDownList(_context);
return Page();
}

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

public async Task<IActionResult> OnPostAsync()


{
var emptyCourse = new Course();

if (await TryUpdateModelAsync<Course>(
emptyCourse,
"course", // Prefix for form value.
s => s.CourseID, s => s.DepartmentID, s => s.Title, s =>
s.Credits))
{
_context.Courses.Add(emptyCourse);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

// Select DepartmentID if TryUpdateModelAsync fails.


PopulateDepartmentsDropDownList(_context,
emptyCourse.DepartmentID);
return Page();
}
}
}

Se quiser ver os comentários de código traduzidos para idiomas diferentes do inglês,


informe-nos neste problema de discussão do GitHub .

O código anterior:
Deriva de DepartmentNamePageModel .
Usa TryUpdateModelAsync para impedir o excesso de postagem.
Remove ViewData["DepartmentID"] . O DepartmentNameSL SelectList modelo é
fortemente tipado e será usado pela Razor página. Modelos fortemente tipados
são preferíveis aos fracamente tipados. Para obter mais informações, consulte
Dados fracamente tipados (ViewData e ViewBag).

Atualizar a página Criar Razor Curso


Atualize Pages/Courses/Create.cshtml com o seguinte código:

CSHTML

@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Course.CourseID" class="control-label">
</label>
<input asp-for="Course.CourseID" class="form-control" />
<span asp-validation-for="Course.CourseID" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Title" class="control-label"></label>
<input asp-for="Course.Title" class="form-control" />
<span asp-validation-for="Course.Title" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Course.Credits" class="control-label">
</label>
<input asp-for="Course.Credits" class="form-control" />
<span asp-validation-for="Course.Credits" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Department" class="control-label">
</label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="Course.DepartmentID" class="text-
danger" />
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

O código anterior faz as seguintes alterações:

Altera a legenda de DepartmentID para Departamento.


Substitui "ViewBag.DepartmentID" por DepartmentNameSL (da classe base).
Adiciona a opção "Selecionar Departamento". Essa alteração renderiza "Selecionar
Departamento" na lista suspensa quando nenhum departamento foi selecionado
ainda, em vez do primeiro departamento.
Adiciona uma mensagem de validação quando o departamento não está
selecionado.

A Razor página usa o Auxiliar de Seleção de Marca:

CSHTML

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

Teste a página Criar. A página Criar exibe o nome do departamento em vez de a ID do


departamento.
Atualizar o modelo da página Editar do Curso
Atualize Pages/Courses/Edit.cshtml.cs com o seguinte código:

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
public class EditModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

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

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


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

Course = await _context.Courses


.Include(c => c.Department).FirstOrDefaultAsync(m =>
m.CourseID == id);

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

// Select current DepartmentID.


PopulateDepartmentsDropDownList(_context, Course.DepartmentID);
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}

var courseToUpdate = await _context.Courses.FindAsync(id);

if (courseToUpdate == 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");
}

// Select DepartmentID if TryUpdateModelAsync fails.


PopulateDepartmentsDropDownList(_context,
courseToUpdate.DepartmentID);
return Page();
}
}
}

As alterações são semelhantes às feitas no modelo da página Criar. No código anterior,


PopulateDepartmentsDropDownList passa a ID do departamento, que seleciona o

departamento na lista suspensa.

Atualizar a página Editar Razor Curso


Atualize Pages/Courses/Edit.cshtml com o seguinte código:

CSHTML

@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-primary" />
</div>
</form>
</div>
</div>

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

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

O código anterior faz as seguintes alterações:

Exibe a ID do curso. Geralmente, a PK (chave primária) de uma entidade não é


exibida. Em geral, PKs não têm sentido para os usuários. Nesse caso, o PK é o
número do curso.
Altera a legenda da lista suspensa Departamento de DepartmentID para
Departamento.
"ViewBag.DepartmentID" Substitui por DepartmentNameSL , que está na classe base.

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 que o
número do curso seja incluído nos dados postados quando o usuário seleciona Salvar.

Atualizar os modelos de página do Curso


AsNoTracking pode melhorar o desempenho quando o acompanhamento não é
necessário.

Atualizar Pages/Courses/Delete.cshtml.cs e Pages/Courses/Details.cshtml.cs adicionar


AsNoTracking aos OnGetAsync métodos:

C#

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


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

Course = await _context.Courses


.AsNoTracking()
.Include(c => c.Department)
.FirstOrDefaultAsync(m => m.CourseID == id);

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

Atualizar as páginas do Curso Razor


Atualize Pages/Courses/Delete.cshtml com o seguinte código:

CSHTML

@page
@model ContosoUniversity.Pages.Courses.DeleteModel
@{
ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

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


<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Department.Name)
</dd>
</dl>

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

Faça as mesmas alterações na página Detalhes.

CSHTML

@page
@model ContosoUniversity.Pages.Courses.DetailsModel

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

<h2>Details</h2>

<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Department.Name)
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>

Testar as páginas Curso


Teste as páginas criar, editar, detalhes e excluir.

Atualizar as páginas Criar e Editar do instrutor


Os instrutores podem ministrar a quantidade de cursos que desejarem. A imagem a
seguir mostra a página Editar do instrutor com uma matriz de caixas de seleção do
curso.
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 são selecionados. O usuário pode marcar ou desmarcar as caixas
de seleção para alterar as atribuições de curso. Se o número de cursos fosse muito
maior, uma interface do usuário diferente poderia funcionar melhor. Porém, o método
de gerenciar uma relação muitos para muitos mostrada aqui não mudaria. Para criar ou
excluir relacionamentos, você manipula uma entidade de junção.

Criar uma classe para dados de cursos atribuídos


Crie Models/SchoolViewModels/AssignedCourseData.cs com o seguinte código:

C#

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

A AssignedCourseData classe contém dados para criar as caixas de seleção para cursos
atribuídos a um instrutor.

Criar uma classe base de modelo de página do Instrutor


Crie a Pages/Instructors/InstructorCoursesPageModel.cs classe base:

C#

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
public class InstructorCoursesPageModel : PageModel
{
public List<AssignedCourseData> AssignedCourseDataList;

public void PopulateAssignedCourseData(SchoolContext context,


Instructor instructor)
{
var allCourses = context.Courses;
var instructorCourses = new HashSet<int>(
instructor.Courses.Select(c => c.CourseID));
AssignedCourseDataList = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
AssignedCourseDataList.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
}
}
}
É InstructorCoursesPageModel a classe base 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 pesquisas eficientes.

Processar o local do escritório


Outra relação que a página de edição precisa tratar é a relação de um para zero ou um
que a entidade Instrutor tem com a entidade OfficeAssignment . O código de edição do
instrutor deve lidar com os seguintes cenários:

Se o usuário limpar a atribuição de escritório, exclua a entidade OfficeAssignment .


Se o usuário inserir uma atribuição de escritório e ela estava vazia, crie uma nova
entidade OfficeAssignment .
Se o usuário alterar a atribuição de escritório, atualize a entidade
OfficeAssignment .

Atualizar o modelo de página Editar do


Instrutor
Atualize Pages/Instructors/Edit.cshtml.cs com o seguinte código:

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class EditModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Instructor Instructor { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Instructor = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

if (Instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(_context, Instructor);
return Page();
}

public async Task<IActionResult> OnPostAsync(int? id, string[]


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

var instructorToUpdate = await _context.Instructors


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

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

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(selectedCourses,
instructorToUpdate);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(_context, instructorToUpdate);
return Page();
}

public void UpdateInstructorCourses(string[] selectedCourses,


Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.Courses.Select(c => c.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
var courseToRemove =
instructorToUpdate.Courses.Single(
c => c.CourseID ==
course.CourseID);
instructorToUpdate.Courses.Remove(courseToRemove);
}
}
}
}
}
}

O código anterior:

Obtém a entidade atual Instructor do banco de dados usando o carregamento


ansioso para as propriedades e Courses navegação OfficeAssignment .
Atualiza a entidade Instructor recuperada com valores do associador de modelos.
TryUpdateModelAsync 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.
Chama PopulateAssignedCourseData em OnGetAsync para fornecer informações
para as caixas de seleção usando a classe do modelo de exibição
AssignedCourseData .

Chama UpdateInstructorCourses em OnPostAsync para aplicar informações das


caixas de seleção à entidade do instrutor que está sendo editada.
Chamará PopulateAssignedCourseData e UpdateInstructorCourses em OnPostAsync
se TryUpdateModelAsync falhar. Essas chamadas de método restauram os dados
de curso atribuídos inseridos na página quando são exibidos novamente com uma
mensagem de erro.

Como a Razor página não tem uma coleção de entidades Course, o associador de
modelo não pode atualizar automaticamente a Courses propriedade de navegação. Em
vez de usar o associador de modelo para atualizar a Courses propriedade de
navegação, isso é feito no novo UpdateInstructorCourses método. Portanto, você
precisa excluir a propriedade Courses do model binding. Isso não requer nenhuma
alteração no código que chama TryUpdateModelAsync porque você está usando a
sobrecarga com propriedades declaradas e Courses não está na lista de inclusão.

Se nenhuma caixa de seleção tiver sido selecionada, o código em


UpdateInstructorCourses inicializará com instructorToUpdate.Courses uma coleção

vazia e retornará:

C#

if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}

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 página. Para facilitar pesquisas eficientes, as últimas
duas coleções são armazenadas em objetos HashSet .

Se a caixa de seleção de um curso for selecionada, mas o curso não estiver na


Instructor.Courses propriedade de navegação, o curso será adicionado à coleção na

propriedade de navegação.

C#

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}

Se a caixa de seleção de um curso não estiver selecionada, mas o curso estiver na


Instructor.Courses propriedade de navegação, o curso será removido da propriedade
de navegação.

C#

else
{
if (instructorCourses.Contains(course.CourseID))
{
var courseToRemove = instructorToUpdate.Courses.Single(
c => c.CourseID == course.CourseID);
instructorToUpdate.Courses.Remove(courseToRemove);
}
}

Atualizar a página Editar Razor Instrutor


Atualize Pages/Instructors/Edit.cshtml com o seguinte código:

CSHTML

@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="table">
<table>
<tr>
@{
int cnt = 0;

foreach (var course in


Model.AssignedCourseDataList)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ?
"checked=\"checked\"" : "")) />
@course.CourseID @:
@course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>

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

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

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 são selecionados.

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.

Execute o aplicativo e teste a página Editar de Instrutores atualizada. Altere algumas


atribuições de curso. As alterações são refletidas na página Índice.

Atualizar a página Criar do Instrutor


Atualize o modelo de página Criar Instrutor e com código semelhante à página Editar:

C#

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class CreateModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
private readonly ILogger<InstructorCoursesPageModel> _logger;

public CreateModel(SchoolContext context,


ILogger<InstructorCoursesPageModel> logger)
{
_context = context;
_logger = logger;
}

public IActionResult OnGet()


{
var instructor = new Instructor();
instructor.Courses = new List<Course>();

// Provides an empty collection for the foreach loop


// foreach (var course in Model.AssignedCourseDataList)
// in the Create Razor page.
PopulateAssignedCourseData(_context, instructor);
return Page();
}

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

public async Task<IActionResult> OnPostAsync(string[]


selectedCourses)
{
var newInstructor = new Instructor();

if (selectedCourses.Length > 0)
{
newInstructor.Courses = new List<Course>();
// Load collection with one DB call.
_context.Courses.Load();
}

// Add selected Courses courses to the new instructor.


foreach (var course in selectedCourses)
{
var foundCourse = await
_context.Courses.FindAsync(int.Parse(course));
if (foundCourse != null)
{
newInstructor.Courses.Add(foundCourse);
}
else
{
_logger.LogWarning("Course {course} not found", course);
}
}

try
{
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");
}
return RedirectToPage("./Index");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}

PopulateAssignedCourseData(_context, newInstructor);
return Page();
}
}
}

O código anterior:

Adiciona log para mensagens de aviso e erro.

Chamadas Load, que busca todos os Cursos em uma chamada de banco de dados.
Para coleções pequenas, essa é uma otimização ao usar FindAsync. FindAsync
retorna a entidade controlada sem uma solicitação ao banco de dados.

C#

public async Task<IActionResult> OnPostAsync(string[] selectedCourses)


{
var newInstructor = new Instructor();

if (selectedCourses.Length > 0)
{
newInstructor.Courses = new List<Course>();
// Load collection with one DB call.
_context.Courses.Load();
}

// Add selected Courses courses to the new instructor.


foreach (var course in selectedCourses)
{
var foundCourse = await
_context.Courses.FindAsync(int.Parse(course));
if (foundCourse != null)
{
newInstructor.Courses.Add(foundCourse);
}
else
{
_logger.LogWarning("Course {course} not found", course);
}
}

try
{
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");
}
return RedirectToPage("./Index");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}

PopulateAssignedCourseData(_context, newInstructor);
return Page();
}

_context.Instructors.Add(newInstructor) cria um novo Instructor uso de


relações muitos para muitos sem mapear explicitamente a tabela de junção.
Muitos para muitos foram adicionados no EF 5.0.

Teste a página Criar Instrutor.

Atualize a página Criar Razor Instrutor com código semelhante à página Editar:

CSHTML

@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="table">
<table>
<tr>
@{
int cnt = 0;

foreach (var course in


Model.AssignedCourseDataList)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ?
"checked=\"checked\"" : "")) />
@course.CourseID @:
@course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>

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

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

Atualizar a página Excluir do Instrutor


Atualize Pages/Instructors/Delete.cshtml.cs com o seguinte código:

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public DeleteModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Instructor Instructor { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Instructor = await _context.Instructors.FirstOrDefaultAsync(m =>


m.ID == id);

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

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


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

Instructor instructor = await _context.Instructors


.Include(i => i.Courses)
.SingleAsync(i => i.ID == id);

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

var departments = await _context.Departments


.Where(d => d.InstructorID == id)
.ToListAsync();
departments.ForEach(d => d.InstructorID = null);

_context.Instructors.Remove(instructor);

await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}

O código anterior faz as seguintes alterações:

Usa o carregamento adiantado para a propriedade de navegação Courses .


Courses deve ser incluído ou eles não são excluídos quando o instrutor é excluído.

Para evitar a necessidade de lê-las, configure a exclusão em cascata no banco de


dados.
Se o instrutor a ser excluído é atribuído como administrador de qualquer
departamento, remove a atribuição de instrutor desse departamento.

Execute o aplicativo e teste a página Excluir.

Próximas etapas
Tutorial anterior Próximo tutorial
Parte 8, Razor Páginas com EF Core em
ASP.NET Core – Simultaneidade
Artigo • 28/11/2022 • 64 minutos para o fim da leitura

Tom Dykstra e Jon P Smith

O aplicativo Web da Contoso University demonstra como criar Razor aplicativos Web de
páginas usando EF Core e o Visual Studio. Para obter informações sobre a série de
tutoriais, consulte o primeiro tutorial.

Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e
compare esse código com o que você criou seguindo o tutorial.

Este tutorial mostra como lidar com conflitos quando vários usuários atualizam uma
entidade simultaneamente.

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 banco de dados.

Se a detecção de simultaneidade não estiver habilitada, quem atualizar o banco de


dados por último substituirá as alterações do outro usuário. Se esse risco for aceitável, o
custo de programação para simultaneidade poderá superar o benefício.

Simultaneidade pessimista
Uma maneira de evitar conflitos de simultaneidade é usar bloqueios de banco de dados.
Isso é chamado de simultaneidade pessimista. Antes que o aplicativo leia uma linha de
banco de dados que ele pretende atualizar, ele solicita um bloqueio. Depois que uma
linha é bloqueada para acesso de atualização, nenhum outro usuário tem permissão
para bloquear a linha até que o primeiro bloqueio seja liberado.

O gerenciamento de bloqueios traz desvantagens. Pode ser complexo de programar e


causar problemas de desempenho conforme o número de usuários aumenta. O Entity
Framework Core não fornece suporte interno para simultaneidade pessimista.
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.
Jane clica em Salvar primeiro e vê que sua alteração entrará em vigor, já que o
navegador exibe a página de Índice com zero como o valor do Orçamento.

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:

Mantenha o controle de qual propriedade um usuário modificou e atualize apenas


as colunas correspondentes no banco de dados.

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
tem algumas desvantagens:
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.

Que a mudança de John substitua a mudança de Jane.

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 que está no armazenamento de dados. O código scaffolded não faz tratamento
de simultaneidade, o Client Wins ocorre automaticamente.

Impedir que a alteração de John seja atualizada no banco de dados. 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 de


armazenamento de dados têm precedência sobre os valores enviados pelo cliente.
O cenário de Vitórias da Loja é usado neste tutorial. Esse método garante que
nenhuma alteração é substituída sem que um usuário seja alertado.

Detecção de conflitos em EF Core


As propriedades configuradas como tokens de simultaneidade são usadas para
implementar o controle de simultaneidade otimista. Quando uma operação de
atualização ou exclusão é disparada por SaveChanges ou SaveChangesAsync, o valor do
token de simultaneidade no banco de dados é comparado com o valor original lido por
EF Core:

Se os valores coincidirem, a operação pode ser concluída.


Se os valores não corresponderem, EF Core pressupõe que outro usuário tenha
realizado uma operação conflitante, anule a transação atual e gere um
DbUpdateConcurrencyException.

Outro usuário ou processo que executa uma operação que entra em conflito com a
operação atual é conhecido como conflito de simultaneidade.
Em bancos de dados relacionaisEF Core, verifica o valor do token de simultaneidade na
WHERE cláusula e DELETE instruções UPDATE para detectar um conflito de simultaneidade.

O modelo de dados deve ser configurado para habilitar a detecção de conflitos,


incluindo uma coluna de acompanhamento que pode ser usada para determinar
quando uma linha foi alterada. O EF fornece duas abordagens para tokens de
simultaneidade:

Aplicando [ConcurrencyCheck] ou IsConcurrencyToken a uma propriedade no


modelo. Essa abordagem não é recomendada. Para obter mais informações,
consulte Tokens de simultaneidade em EF Core.

Aplicando TimestampAttribute ou IsRowVersion a um token de simultaneidade no


modelo. Essa é a abordagem usada neste tutorial.

A abordagem SQL Server e os detalhes da implementação do SQLite são ligeiramente


diferentes. Um arquivo de diferença é mostrado posteriormente no tutorial listando as
diferenças. A guia do Visual Studio mostra a abordagem SQL Server. A guia Visual
Studio Code mostra a abordagem para bancos de dados não SQL Server, como SQLite.

Visual Studio

No modelo, inclua uma coluna de acompanhamento que é usada para


determinar quando uma linha foi alterada.
Aplique a TimestampAttribute propriedade de simultaneidade.

Atualize o Models/Department.cs arquivo com o seguinte código realçado:

C#

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

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

[Timestamp]
public byte[] ConcurrencyToken { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

É TimestampAttribute o que identifica a coluna como uma coluna de


acompanhamento de simultaneidade. A API fluente é uma maneira alternativa de
especificar a propriedade de acompanhamento:

C#

modelBuilder.Entity<Department>()
.Property<byte[]>("ConcurrencyToken")
.IsRowVersion();

O [Timestamp] atributo em uma propriedade de entidade gera o seguinte código


no ModelBuilder método:

C#

b.Property<byte[]>("ConcurrencyToken")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");

O código anterior:

Define o tipo ConcurrencyToken de propriedade como matriz de bytes.


byte[] é o tipo necessário para SQL Server.
Chama IsConcurrencyToken. IsConcurrencyToken configura a propriedade
como um token de simultaneidade. Nas atualizações, o valor do token de
simultaneidade no banco de dados é comparado com o valor original para
garantir que ele não tenha sido alterado desde que a instância foi recuperada
do banco de dados. Se tiver sido alterado, será gerado um
DbUpdateConcurrencyException lançamento e as alterações não serão
aplicadas.
Chamadas ValueGeneratedOnAddOrUpdate, que configuram a
ConcurrencyToken propriedade para ter um valor gerado automaticamente ao
adicionar ou atualizar uma entidade.
HasColumnType("rowversion") define o tipo de coluna no banco de dados SQL

Server para rowversion.

O código a seguir mostra uma parte do T-SQL gerada quando EF Core o


Department nome é atualizado:

SQL

SET NOCOUNT ON;


UPDATE [Departments] SET [Name] = @p0
WHERE [DepartmentID] = @p1 AND [ConcurrencyToken] = @p2;
SELECT [ConcurrencyToken]
FROM [Departments]
WHERE @@ROWCOUNT = 1 AND [DepartmentID] = @p1;

O código anterior realçado mostra a cláusula WHERE que contém ConcurrencyToken .


Se o banco de dados ConcurrencyToken não for igual ao ConcurrencyToken
parâmetro @p2 , nenhuma linha será atualizada.

O seguinte código realçado mostra o T-SQL que verifica exatamente se uma linha
foi atualizada:

SQL

SET NOCOUNT ON;


UPDATE [Departments] SET [Name] = @p0
WHERE [DepartmentID] = @p1 AND [ConcurrencyToken] = @p2;
SELECT [ConcurrencyToken]
FROM [Departments]
WHERE @@ROWCOUNT = 1 AND [DepartmentID] = @p1;

@@ROWCOUNT retorna o número de linhas afetadas pela última instrução. Se


nenhuma linha for atualizada, EF Core gerará um DbUpdateConcurrencyException .

Adicionar uma migração


Adicionar a propriedade ConcurrencyToken muda o modelo de dados, o que exige uma
migração.
Compile o projeto.

Visual Studio

Executar os seguintes comandos no PMC:

PowerShell

Add-Migration RowVersion
Update-Database

Os comandos anteriores:

Cria o Migrations/{time stamp}_RowVersion.cs arquivo de migração.


Atualizações o Migrations/SchoolContextModelSnapshot.cs arquivo. A
atualização adiciona o seguinte código ao BuildModel método:

C#

b.Property<byte[]>("ConcurrencyToken")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");

Aplicar scaffold a páginas de Departamento


Visual Studio

Siga as instruções em páginas do aluno do Scaffold com as seguintes exceções:

Crie uma pasta Pages/Departments.


Use Department para a classe de modelo.
Use a classe de contexto existente, em vez de criar uma nova.

Adicionar uma classe de utilitário


Na pasta do projeto, crie a Utility classe com o seguinte código:

Visual Studio
C#

namespace ContosoUniversity
{
public static class Utility
{
public static string GetLastChars(byte[] token)
{
return token[7].ToString();
}
}
}

A Utility classe fornece o GetLastChars método usado para exibir os últimos


caracteres do token de simultaneidade. O código a seguir mostra o código que funciona
com o anúncio do SQLite SQL Server:

C#

#if SQLiteVersion
using System;

namespace ContosoUniversity
{
public static class Utility
{
public static string GetLastChars(Guid token)
{
return token.ToString().Substring(
token.ToString().Length - 3);
}
}
}
#else
namespace ContosoUniversity
{
public static class Utility
{
public static string GetLastChars(byte[] token)
{
return token[7].ToString();
}
}
}
#endif

A #if SQLiteVersion diretiva de pré-processador isola as diferenças nas versões SQLite e


SQL Server e ajuda:
O autor mantém uma base de código para ambas as versões.
Os desenvolvedores do SQLite implantam o aplicativo no Azure e usam SQL Azure.

Compile o projeto.

Atualize a página Índice


A ferramenta de scaffolding criou uma coluna ConcurrencyToken para a página de
índice, mas esse campo não seria exibido em um aplicativo de produção. Neste tutorial,
a última parte do ConcurrencyToken é exibida para ajudar a mostrar como o tratamento
de simultaneidade funciona. A última parte não tem garantia de ser exclusiva por si só.

Atualize a página Pages\Departments\Index.cshtml:

Substitua Índice por Departamentos.


Altere o código que contém ConcurrencyToken para mostrar apenas os últimos
caracteres.
Substitua FirstMidName por FullName .

O código a seguir mostra a página atualizada:

CSHTML

@page
@model ContosoUniversity.Pages.Departments.IndexModel

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

<h2>Departments</h2>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Department[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].StartDate)
</th>
<th>
@Html.DisplayNameFor(model =>
model.Department[0].Administrator)
</th>
<th>
Token
</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>
@Utility.GetLastChars(item.ConcurrencyToken)
</td>
<td>
<a asp-page="./Edit" asp-route-
id="@item.DepartmentID">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.DepartmentID">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.DepartmentID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Atualizar o modelo da página Editar


Atualize Pages/Departments/Edit.cshtml.cs com o seguinte código:

Visual Studio

C#
using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Departments
{
public class EditModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Department Department { get; set; }
// Replace ViewData["InstructorID"]
public SelectList InstructorNameSL { get; set; }

public async Task<IActionResult> OnGetAsync(int id)


{
Department = await _context.Departments
.Include(d => d.Administrator) // eager loading
.AsNoTracking() // tracking not required
.FirstOrDefaultAsync(m => m.DepartmentID == id);

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

// Use strongly typed data rather than ViewData.


InstructorNameSL = new SelectList(_context.Instructors,
"ID", "FirstMidName");

return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


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

// Fetch current department from DB.


// ConcurrencyToken may have changed.
var departmentToUpdate = await _context.Departments
.Include(i => i.Administrator)
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}

// Set ConcurrencyToken to value read in OnGetAsync


_context.Entry(departmentToUpdate).Property(
d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;

if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"Department",
s => s.Name, s => s.StartDate, s => s.Budget, s =>
s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues =
(Department)exceptionEntry.Entity;
var databaseEntry =
exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable
to save. " +
"The department was deleted by another
user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await setDbErrorMessage(dbValues, clientValues,
_context);

// Save the current ConcurrencyToken so next


postback
// matches unless an new concurrency issue happens.
Department.ConcurrencyToken =
(byte[])dbValues.ConcurrencyToken;
// Clear the model error for the next postback.
ModelState.Remove($"{nameof(Department)}.
{nameof(Department.ConcurrencyToken)}");
}
}
InstructorNameSL = new SelectList(_context.Instructors,
"ID", "FullName", departmentToUpdate.InstructorID);

return Page();
}

private IActionResult HandleDeletedDepartment()


{
var deletedDepartment = new Department();
// ModelState contains the posted data because of the
deletion error
// and overides the Department instance values when
displaying Page().
ModelState.AddModelError(string.Empty,
"Unable to save. The department was deleted by another
user.");
InstructorNameSL = new SelectList(_context.Instructors,
"ID", "FullName", Department.InstructorID);
return Page();
}

private async Task setDbErrorMessage(Department dbValues,


Department clientValues, SchoolContext context)
{

if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Department.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Department.Budget",
$"Current value: {dbValues.Budget:c}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Department.StartDate",
$"Current value: {dbValues.StartDate:d}");
}
if (dbValues.InstructorID != clientValues.InstructorID)
{
Instructor dbInstructor = await _context.Instructors
.FindAsync(dbValues.InstructorID);
ModelState.AddModelError("Department.InstructorID",
$"Current value: {dbInstructor?.FullName}");
}

ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in
the database "
+ "have been displayed. If you still want to edit this
record, click "
+ "the Save button again.");
}
}
}

As atualizações de simultaneidade
OriginalValue é atualizado com o ConcurrencyToken valor da entidade quando ele foi
buscado no OnGetAsync método. EF Core gera um SQL UPDATE comando com uma
WHERE cláusula que contém o valor original ConcurrencyToken . Se nenhuma linha for

afetada pelo UPDATE comando, uma DbUpdateConcurrencyException exceção será


lançada. Nenhuma linha é afetada pelo UPDATE comando quando nenhuma linha tem o
valor original ConcurrencyToken .

Visual Studio

C#

public async Task<IActionResult> OnPostAsync(int id)


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

// Fetch current department from DB.


// ConcurrencyToken may have changed.
var departmentToUpdate = await _context.Departments
.Include(i => i.Administrator)
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}

// Set ConcurrencyToken to value read in OnGetAsync


_context.Entry(departmentToUpdate).Property(
d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;

No código realçado anterior:

O valor é Department.ConcurrencyToken o valor quando a entidade foi buscada na


solicitação Get da Edit página. O valor é fornecido ao OnPost método por um
campo oculto na Razor página que exibe a entidade a ser editada. O valor do
campo oculto é copiado para Department.ConcurrencyToken pelo associador de
modelos.
OriginalValue é o que EF Core usa na WHERE cláusula. Antes que a linha de código
realçada seja executada:
OriginalValue tem o valor que estava no banco de dados quando

FirstOrDefaultAsync foi chamado neste método.


Esse valor pode ser diferente do que foi exibido na página Editar.
O código realçado garante que EF Core use o valor original ConcurrencyToken da
entidade exibida Department na cláusula da WHERE instrução SQL UPDATE .

O código a seguir mostra o Department modelo. Department é inicializado no:

OnGetAsync método pela consulta EF.


OnPostAsync pelo campo oculto na página usando a Razorassociação de modelo:

Visual Studio

C#

public class EditModel : PageModel


{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Department Department { get; set; }
// Replace ViewData["InstructorID"]
public SelectList InstructorNameSL { get; set; }

public async Task<IActionResult> OnGetAsync(int id)


{
Department = await _context.Departments
.Include(d => d.Administrator) // eager loading
.AsNoTracking() // tracking not required
.FirstOrDefaultAsync(m => m.DepartmentID == id);

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

// Use strongly typed data rather than ViewData.


InstructorNameSL = new SelectList(_context.Instructors,
"ID", "FirstMidName");

return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


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

// Fetch current department from DB.


// ConcurrencyToken may have changed.
var departmentToUpdate = await _context.Departments
.Include(i => i.Administrator)
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}

// Set ConcurrencyToken to value read in OnGetAsync


_context.Entry(departmentToUpdate).Property(
d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;

O código anterior mostra que o ConcurrencyToken valor da Department entidade da


solicitação HTTP POST é definido como o ConcurrencyToken valor da solicitação HTTP GET
.

Quando ocorre um erro de simultaneidade, o código realçado a seguir obtém os valores


do cliente (os valores postados para esse método) e os valores do banco de dados.

C#

if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"Department",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await setDbErrorMessage(dbValues, clientValues, _context);

// Save the current ConcurrencyToken so next postback


// matches unless an new concurrency issue happens.
Department.ConcurrencyToken = dbValues.ConcurrencyToken;
// Clear the model error for the next postback.
ModelState.Remove($"{nameof(Department)}.
{nameof(Department.ConcurrencyToken)}");
}

O seguinte código adiciona uma mensagem de erro personalizada a cada coluna que
tem valores de banco de dados diferentes daqueles que foram postados em
OnPostAsync :

C#

private async Task setDbErrorMessage(Department dbValues,


Department clientValues, SchoolContext context)
{

if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Department.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Department.Budget",
$"Current value: {dbValues.Budget:c}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Department.StartDate",
$"Current value: {dbValues.StartDate:d}");
}
if (dbValues.InstructorID != clientValues.InstructorID)
{
Instructor dbInstructor = await _context.Instructors
.FindAsync(dbValues.InstructorID);
ModelState.AddModelError("Department.InstructorID",
$"Current value: {dbInstructor?.FullName}");
}
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database
"
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}

O código realçado a seguir define o valor ConcurrencyToken com o novo valor


recuperado do banco de dados. 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.

C#

if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"Department",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await setDbErrorMessage(dbValues, clientValues, _context);

// Save the current ConcurrencyToken so next postback


// matches unless an new concurrency issue happens.
Department.ConcurrencyToken = dbValues.ConcurrencyToken;
// Clear the model error for the next postback.
ModelState.Remove($"{nameof(Department)}.
{nameof(Department.ConcurrencyToken)}");
}

A ModelState.Remove instrução é necessária porque ModelState tem o valor anterior


ConcurrencyToken . Razor Na Página, o ModelState valor de um campo tem precedência
sobre os valores de propriedade do modelo quando ambos estão presentes.

diferenças de código SQL Server versus SQLite


O seguinte mostra as diferenças entre as versões SQL Server e SQLite:

diff

+ using System; // For GUID on SQLite

+ departmentToUpdate.ConcurrencyToken = Guid.NewGuid();

_context.Entry(departmentToUpdate)
.Property(d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;

- Department.ConcurrencyToken = (byte[])dbValues.ConcurrencyToken;
+ Department.ConcurrencyToken = dbValues.ConcurrencyToken;

Atualizar a página Editar Razor


Atualize Pages/Departments/Edit.cshtml com o seguinte código:

CSHTML

@page "{id:int}"
@model ContosoUniversity.Pages.Departments.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="Department.DepartmentID" />
<input type="hidden" asp-for="Department.ConcurrencyToken" />
<div class="form-group">
<label>Version</label>
@Utility.GetLastChars(Model.Department.ConcurrencyToken)
</div>
<div class="form-group">
<label asp-for="Department.Name" class="control-label">
</label>
<input asp-for="Department.Name" class="form-control" />
<span asp-validation-for="Department.Name" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Department.Budget" class="control-label">
</label>
<input asp-for="Department.Budget" class="form-control" />
<span asp-validation-for="Department.Budget" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Department.StartDate" class="control-label">
</label>
<input asp-for="Department.StartDate" class="form-control"
/>
<span asp-validation-for="Department.StartDate" class="text-
danger">
</span>
</div>
<div class="form-group">
<label class="control-label">Instructor</label>
<select asp-for="Department.InstructorID" class="form-
control"
asp-items="@Model.InstructorNameSL"></select>
<span asp-validation-for="Department.InstructorID"
class="text-danger">
</span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

O código anterior:

Atualiza a diretiva page de @page para @page "{id:int}" .


Adiciona uma versão de linha oculta. ConcurrencyToken deve ser adicionado para
que o postback associe o valor.
Exibe o último byte de ConcurrencyToken para fins de depuração.
Substitui ViewData pelo InstructorNameSL fortemente tipado.

Testar conflitos de simultaneidade com a página Editar


Abra duas instâncias de navegadores de Editar no departamento de inglês:
Execute o aplicativo e selecione Departamentos.
Clique com o botão direito do mouse no hiperlink Editar do departamento de
inglês e selecione Abrir em uma nova guia.
Na primeira guia, clique no hiperlink Editar do departamento de inglês.

As duas guias do navegador exibem as mesmas informações.

Altere o nome na primeira guia do navegador e clique em Salvar.

O navegador mostra a página Índice com o valor alterado e o indicador atualizado


ConcurrencyToken . Observe que o indicador atualizado ConcurrencyToken é 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 banco de dados:
Essa janela do navegador não pretendia alterar o campo Name. Copie e cole o valor
atual (Languages) para o campo Name. Tab out. A validação do lado do cliente remove
a mensagem de erro.

Clique em Salvar novamente. O valor inserido na segunda guia do navegador foi salvo.
Você verá os valores salvos na página Índice.

Atualizar o modelo de página Excluir


Atualize Pages/Departments/Delete.cshtml.cs com o seguinte código:

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Departments
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public DeleteModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Department Department { get; set; }
public string ConcurrencyErrorMessage { get; set; }

public async Task<IActionResult> OnGetAsync(int id, bool?


concurrencyError)
{
Department = await _context.Departments
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);

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

if (concurrencyError.GetValueOrDefault())
{
ConcurrencyErrorMessage = "The record you attempted to
delete "
+ "was modified by another user after you selected delete.
"
+ "The delete operation was canceled and the current
values in the "
+ "database have been displayed. If you still want to
delete this "
+ "record, click the Delete button again.";
}
return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
try
{
if (await _context.Departments.AnyAsync(
m => m.DepartmentID == id))
{
// Department.ConcurrencyToken value is from when the
entity
// was fetched. If it doesn't match the DB, a
// DbUpdateConcurrencyException exception is thrown.
_context.Departments.Remove(Department);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToPage("./Delete",
new { concurrencyError = true, id = id });
}
}
}
}

A página Excluir detectou conflitos de simultaneidade quando a entidade foi alterada


depois de ser buscada. Department.ConcurrencyToken é a versão de linha quando a
entidade foi buscada. Quando EF Core cria o SQL DELETE comando, ele inclui uma
cláusula WHERE com ConcurrencyToken . Se o SQL DELETE comando resultar em zero
linhas afetadas:

SQL DELETE O ConcurrencyToken comando não corresponde ConcurrencyToken no


banco de dados.
Uma DbUpdateConcurrencyException exceção é gerada.
OnGetAsync é chamado com o concurrencyError .

Atualizar a página Excluir Razor


Atualize Pages/Departments/Delete.cshtml com o seguinte código:

CSHTML

@page "{id:int}"
@model ContosoUniversity.Pages.Departments.DeleteModel

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

<h1>Delete</h1>

<p class="text-danger">@Model.ConcurrencyErrorMessage</p>

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


<div>
<h4>Department</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.Name)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Name)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.Budget)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Budget)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.StartDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.StartDate)
</dd>
<dt class="col-sm-2">
@Utility.GetLastChars(Model.Department.ConcurrencyToken)
</dt>
<dd class="col-sm-10">
@Utility.GetLastChars(Model.Department.ConcurrencyToken)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.Administrator)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model =>
model.Department.Administrator.FullName)
</dd>
</dl>

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

O código anterior faz as seguintes alterações:

Atualiza a diretiva page de @page para @page "{id:int}" .


Adiciona uma mensagem de erro.
Substitua FirstMidName por FullName no campo Administrador.
Altere ConcurrencyToken para exibir o último byte.
Adiciona uma versão de linha oculta. ConcurrencyToken deve ser adicionado para
que o postback associe o valor.
Testar os conflitos de simultaneidade
Crie um departamento de teste.

Abra duas instâncias dos navegadores de Excluir no departamento de teste:

Execute o aplicativo e selecione Departamentos.


Clique com o botão direito do mouse no hiperlink Excluir do departamento de
teste e selecione Abrir em uma nova guia.
Clique no hiperlink Editar do departamento de teste.

As duas guias do navegador exibem as mesmas informações.

Altere o orçamento na primeira guia do navegador e clique em Salvar.

O navegador mostra a página Índice com o valor alterado e o indicador atualizado


ConcurrencyToken . Observe que o indicador atualizado ConcurrencyToken é exibido no

segundo postback na outra guia.

Exclua o departamento de teste da segunda guia. Um erro de simultaneidade é exibido


com os valores atuais do banco de dados. Clicar em Excluir exclui a entidade, a menos
que ConcurrencyToken tenha sido atualizada.

Recursos adicionais
Tokens de simultaneidade em EF Core
Manipular simultaneidade em EF Core
Depuração de origem do ASP.NET Core 2.x

Próximas etapas
Este é o último tutorial da série. Tópicos adicionais são abordados na versão MVC desta
série de tutoriais.

Tutorial anterior
ASP.NET Core MVC com EF Core - série
de tutoriais
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Este tutorial ensina a usar o ASP.NET Core MVC e o Entity Framework Core com
controladores e exibições. Razor Pages é um modelo de programação alternativo. Para
novos desenvolvimentos, recomendamos o Razor Pages em vez do MVC com
controladores e exibições. Consulte a versão do Razor Pages deste tutorial. Cada tutorial
aborda um material diferente:

Este tutorial do MVC traz algumas informações que o do Razor Pages não traz:

Implementar a herança no modelo de dados


Executar consultas SQL brutas
Usar o LINQ dinâmico para simplificar o código

Informações que o tutorial do Razor Pages traz que este não traz:

Usar o método Select para carregar dados relacionados


Melhores práticas para o EF.

1. Introdução
2. Operações de criação, leitura, atualização e exclusão
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
Tutorial: Introdução a EF Core um
aplicativo Web ASP.NET MVC
Artigo • 28/11/2022 • 44 minutos para o fim da leitura

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. Razor Pages é um modelo de programação alternativo. Para
novos desenvolvimentos, recomendamos o Razor Pages em vez do MVC com
controladores e exibições. Consulte a versão do Razor Pages deste tutorial. Cada tutorial
aborda um material diferente:

Este tutorial do MVC traz algumas informações que o do Razor Pages não traz:

Implementar a herança no modelo de dados


Executar consultas SQL brutas
Usar o LINQ dinâmico para simplificar o código

Informações que o tutorial do Razor Pages traz que este não traz:

Usar o método Select para carregar dados relacionados


Melhores práticas para o EF.

O aplicativo Web de exemplo da Contoso University demonstra como criar um


aplicativo Web ASP.NET Core MVC usando o EF (Entity Framework) Core e o Visual
Studio.

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 explicam como criar o aplicativo de
exemplo da Contoso University.

Pré-requisitos
Se você não estiver familiarizado com ASP.NET Core MVC, acesse a série de
tutoriais Introdução ao ASP.NET Core MVC antes de iniciar esta.

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Este tutorial não foi atualizado para ASP.NET Core 6 ou posterior. As instruções do
tutorial não funcionarão corretamente se você criar um projeto direcionado ASP.NET
Core 6 ou 7. Por exemplo, os modelos da Web ASP.NET Core 6 e 7 usam o modelo de
hospedagem mínimo, que unifica Startup.cs e Program.cs em um único Program.cs
arquivo.

Outra diferença introduzida no .NET 6 é o recurso NRT (tipos de referência anuláveis).


Os modelos de projeto habilitam esse recurso por padrão. Podem ocorrer problemas
em que o EF considera uma propriedade a ser necessária no .NET 6 que é anulável no
.NET 5. Por exemplo, a página Criar Aluno falhará silenciosamente, a menos que a
Enrollments propriedade seja tornada anulável ou a asp-validation-summary marca

auxiliar seja alterada de ModelOnly para All .

Recomendamos que você instale e use o SDK do .NET 5 para este tutorial. Até que este
tutorial seja atualizado, consulte Razor Páginas com o Entity Framework Core no
ASP.NET Core – Tutorial 1 de 8 sobre como usar o Entity Framework com ASP.NET Core
6 ou posterior.

Mecanismos de banco de dados


As instruções do Visual Studio usam SQL Server LocalDB, uma versão do SQL Server
Express que é executada somente no Windows.

Resolver problemas e solucionar 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. Se você não encontrar o que precisa lá, poderá postar uma pergunta para
StackOverflow.com para ASP.NET Core ou EF Core .

 Dica

Esta é uma série de dez tutoriais, cada um se baseando no que é feito nos tutoriais
anteriores. Considere a possibilidade de salvar uma cópia do projeto após a
conclusão bem-sucedida de cada tutorial. Caso tenha problemas, comece
novamente no tutorial anterior em vez de voltar ao início de toda a série.

Aplicativo Web Contoso University


O aplicativo criado nesses tutoriais é um site básico de universidade.
Os usuários podem exibir e atualizar informações de alunos, cursos e instrutores. Aqui
estão algumas das telas no aplicativo:
Criar um aplicativo Web
1. Inicie o Visual Studio e selecione Criar um projeto.
2. Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core Aplicativo>
WebAvançar.
3. Na caixa de diálogo Configurar seu novo projeto , insira ContosoUniversity para
Nome do projeto. É importante usar esse nome exato, incluindo capitalização,
para que cada namespace um corresponda quando o código é copiado.
4. Selecione Criar.
5. Na caixa de diálogo Criar um novo aplicativo Web ASP.NET Core, selecione:
a. O .NET Core e o ASP.NET Core 5.0 nas listas suspensas.
b. ASP.NET Core Aplicativo Web (Model-View-Controller).
c. Caixa de diálogo Criar

Configurar o estilo do site


Algumas alterações básicas configuram o menu do site, o layout e a home page.

Abra Views/Shared/_Layout.cshtml e faça as seguintes alterações:

Altere cada ocorrência de ContosoUniversity para Contoso University . Há três


ocorrências.
Adicione entradas de menu para Sobre, Alunos, Cursos, Instrutores e
Departamentos e exclua a entrada do Privacy menu.

As alterações anteriores são realçadas no seguinte código:

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<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-area="" asp-controller="Home"
asp-action="Index">Contoso University</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
justify-content-between">
<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="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Students" asp-action="Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Courses" asp-action="Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Departments" asp-action="Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2020 - Contoso University - <a asp-area="" asp-
controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

No Views/Home/Index.cshtml , substitua o conteúdo do arquivo pela seguinte marcação:

CSHTML

@{
ViewData["Title"] = "Home Page";
}

<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core MVC web application.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series
of tutorials.</p>
<p><a class="btn btn-default"
href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the
tutorial &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default"
href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef
-mvc/intro/samples/5cu-final">See project source code &raquo;</a></p>
</div>
</div>

Pressione CTRL+F5 para executar o projeto ou escolha Depurar > Iniciar sem
Depuração no menu. A home page é exibida com guias para as páginas criadas neste
tutorial.
EF Core Pacotes NuGet
Este tutorial usa o SQL Server e o pacote de provedor é
Microsoft.EntityFrameworkCore.SqlServer .

O pacote de SQL Server EF e suas dependências e


Microsoft.EntityFrameworkCore Microsoft.EntityFrameworkCore.Relational , fornecem
suporte de runtime para EF.

Adicione o pacote NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .


No PMC (Console do Gerenciador de Pacotes), insira os seguintes comandos para
adicionar os pacotes NuGet:

PowerShell

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
O Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacote NuGet fornece
ASP.NET Core middleware para EF Core páginas de erro. Esse middleware ajuda a
detectar e diagnosticar erros com EF Core migrações.

Para obter informações sobre outros provedores de banco de dados disponíveis para EF
Core, consulte Provedores de banco de dados.

Criar o modelo de dados


As seguintes classes de entidade são criadas para este aplicativo:

As entidades anteriores têm as seguintes relações:

Uma relação um-para-muitos entre Student entidades e Enrollment . Um aluno


pode ser matriculado em qualquer número de cursos.
Uma relação um-para-muitos entre Course entidades e Enrollment . Um curso
pode ter qualquer quantidade de alunos registrados.

Nas seções a seguir, uma classe é criada para cada uma dessas entidades.

A entidade Student

Na pasta Modelos , crie a Student classe com o seguinte código:


C#

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }

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


}
}

A ID propriedade é a coluna de chave primária (PK) da tabela de banco de dados que


corresponde a essa classe. Por padrão, o EF interpreta uma propriedade chamada ID ou
classnameID como a chave primária. Por exemplo, o PK pode ser nomeado StudentID

em vez de ID .

A propriedade Enrollments é uma propriedade de navegação. As propriedades de


navegação armazenam outras entidades que estão relacionadas a essa entidade. A
Enrollments propriedade de uma Student entidade:

Contém todas as Enrollment entidades relacionadas a essa Student entidade.


Se uma linha específica Student no banco de dados tiver duas linhas relacionadas
Enrollment :

A Student propriedade de navegação dessa Enrollments entidade contém


essas duas Enrollment entidades.

Enrollment As linhas contêm o valor PK de um aluno na StudentID coluna FK (chave

estrangeira).

Se uma propriedade de navegação puder conter várias entidades:

O tipo deve ser uma lista, como ICollection<T> , List<T> ou HashSet<T> .


As entidades podem ser adicionadas, excluídas e atualizadas.

Relações de navegação muitos para muitos e um para muitos podem conter várias
entidades. Quando ICollection<T> é usado, o EF cria uma HashSet<T> coleção por
padrão.
A entidade Enrollment

Na pasta Modelos , crie a Enrollment classe com o seguinte código:

C#

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

A EnrollmentID propriedade é o PK. Essa entidade usa o classnameID padrão em vez de


ID por si mesma. A Student entidade usou o ID padrão . Alguns desenvolvedores

preferem usar um padrão em todo o modelo de dados. Neste tutorial, a variação ilustra
que qualquer padrão pode ser usado. Um tutorial posterior mostra como usar ID sem
classname facilita a implementação da herança no modelo de dados.

A propriedade Grade é um enum . O ? após a Grade declaração de tipo indica que a


Grade propriedade é anulável. Uma nota diferente null de zero. null significa que uma

nota não é conhecida ou ainda não foi atribuída.


A StudentID propriedade é uma FK (chave estrangeira) e a propriedade de navegação
correspondente é Student . Uma Enrollment entidade é associada a uma Student
entidade, portanto, a propriedade só pode conter uma única Student entidade. Isso
difere da Student.Enrollments propriedade de navegação , que pode conter várias
Enrollment entidades.

A CourseID propriedade é um FK e a propriedade de navegação correspondente é


Course . Uma entidade Enrollment está associada a uma entidade Course .

O Entity Framework interpretará uma propriedade como uma propriedade FK se ela for
nomeada < nome da propriedade de navegação nome >< da propriedade da chave
primária. > Por exemplo, StudentID para a Student propriedade de navegação, já que o
Student PK da entidade é ID . As propriedades FK também podem ser nomeadas como

< nome > da propriedade da chave primária. Por exemplo, CourseID porque o Course
PK da entidade é CourseID .

A entidade Course

Na pasta Modelos , crie a Course classe com o seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }

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


}
}
A propriedade Enrollments é uma propriedade de navegação. Uma entidade Course
pode estar relacionada a qualquer quantidade de entidades Enrollment .

O atributo DatabaseGenerated é explicado em um tutorial posterior. Esse atributo


permite inserir o PK para o curso em vez de fazer com que o banco de dados o gere.

Criar o contexto de banco de dados


A classe principal que coordena a funcionalidade do EF para um determinado modelo
de dados é a classe de contexto do DbContext banco de dados. Essa classe é criada
derivando-a da classe Microsoft.EntityFrameworkCore.DbContext . A DbContext classe
derivada especifica quais entidades são incluídas no modelo de dados. Alguns
comportamentos de EF podem ser personalizados. Neste projeto, a classe é chamada
SchoolContext .

Na pasta do projeto, crie uma pasta chamada Data .

Na pasta Dados , crie uma SchoolContext classe com o seguinte código:

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}

O código anterior cria uma DbSet propriedade para cada conjunto de entidades. Na
terminologia do EF:

Um conjunto de entidades normalmente corresponde a uma tabela de banco de


dados.
Uma entidade corresponde a uma linha da tabela.
As DbSet<Enrollment> instruções e DbSet<Course> poderiam ser omitidas e funcionariam
da mesma forma. O EF os incluiria implicitamente porque:

A Student entidade faz referência à Enrollment entidade .


A Enrollment entidade faz referência à Course entidade .

Quando o banco de dados é criado, o EF cria tabelas que têm nomes iguais aos nomes
de propriedade DbSet . Os nomes de propriedade para coleções normalmente são
plurais. Por exemplo, Students em vez de Student . Os desenvolvedores não concordam
sobre se os nomes de tabela devem ser pluralizados ou não. Para esses tutoriais, o
comportamento padrão é substituído especificando nomes de tabela singulares no
DbContext . Para fazer isso, adicione o código realçado a seguir após a última

propriedade DbSet.

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}

Registrar o SchoolContext
O ASP.NET Core inclui a injeção de dependência. Serviços, como o contexto do banco
de dados EF, são registrados com injeção de dependência durante a inicialização do
aplicativo. Componentes que exigem esses serviços, como controladores MVC, recebem
esses serviços por meio de parâmetros de construtor. O código do construtor do
controlador que obtém uma instância de contexto é mostrado posteriormente neste
tutorial.

Para se registrar SchoolContext como um serviço, abra Startup.cs e adicione as linhas


realçadas ao ConfigureServices método .

C#

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ContosoUniversity
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<SchoolContext>(options =>

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

services.AddControllersWithViews();
}

O nome da cadeia de conexão é passado para o contexto com a chamada de um


método em um objeto DbContextOptionsBuilder . Para desenvolvimento local, o sistema
de configuração ASP.NET Core lê a cadeia de conexão do appsettings.json arquivo.

Abra o appsettings.json arquivo e adicione uma cadeia de conexão, conforme


mostrado na seguinte marcação:

JSON
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;
MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Adicionar o filtro de exceção de banco de dados


Adicione AddDatabaseDeveloperPageExceptionFilter a ConfigureServices conforme
mostrado no seguinte código:

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<SchoolContext>(options =>

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

services.AddDatabaseDeveloperPageExceptionFilter();

services.AddControllersWithViews();
}

O AddDatabaseDeveloperPageExceptionFilter fornece informações de erro úteis no


ambiente de desenvolvimento.

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> .
Inicializar o BD com os dados de teste
O EF cria um banco de dados vazio. Nesta seção, um método é adicionado que é
chamado depois que o banco de dados é criado para preenchê-lo com dados de teste.

O EnsureCreated método é usado para criar automaticamente o banco de dados. Em


um tutorial posterior, você verá como lidar com alterações de modelo usando
Migrações do Code First para alterar o esquema de banco de dados em vez de
descartar e recriar o banco de dados.

Na pasta Dados , crie uma nova classe chamada DbInitializer com o seguinte código:

C#

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();

// Look for any students.


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

var students = new Student[]


{
new
Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.P
arse("2005-09-01")},
new
Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Pa
rse("2002-09-01")},
new
Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse
("2003-09-01")},
new
Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Pa
rse("2002-09-01")},
new
Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002
-09-01")},
new
Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Pars
e("2001-09-01")},
new
Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse
("2003-09-01")},
new
Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Pars
e("2005-09-01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges();

var courses = new Course[]


{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}
O código anterior verifica se o banco de dados existe:

Se o banco de dados não for encontrado;


Ele é criado e carregado com dados de teste. Ele carrega os dados de teste em
matrizes em vez de em coleções List<T> para otimizar o desempenho.
Se o banco de dados for encontrado, ele não executará nenhuma ação.

Atualize Program.cs com o seguinte código:

C#

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();

CreateDbIfNotExists(host);

host.Run();
}

private static void CreateDbIfNotExists(IHost host)


{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>
();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger =
services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the
DB.");
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

Program.cs faz o seguinte na inicialização do aplicativo:

Obtenha uma instância de contexto de banco de dados do contêiner de injeção de


dependência.
Chame o método DbInitializer.Initialize .
Descarte o contexto quando o Initialize método for concluído, conforme
mostrado no código a seguir:

C#

public static void Main(string[] args)


{
var host = CreateWebHostBuilder(args).Build();

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


{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the
database.");
}
}

host.Run();
}

Na primeira vez que o aplicativo é executado, o banco de dados é criado e carregado


com dados de teste. Sempre que o modelo de dados for alterado:

Exclua o banco de dados.


Atualize o método de semente e comece de novo com um novo banco de dados.
Em tutoriais posteriores, o banco de dados é modificado quando o modelo de dados é
alterado, sem excluí-lo e re-criá-lo. Nenhum dado é perdido quando o modelo de
dados é alterado.

Criar um controlador e exibições


Use o mecanismo de scaffolding no Visual Studio para adicionar um controlador MVC e
exibições que usarão o EF para consultar e salvar dados.

A criação automática de métodos e exibições de ação CRUD é conhecida como


scaffolding.

Em Gerenciador de Soluções, clique com o botão direito do mouse na


Controllers pasta e selecione Adicionar > Novo Item Com Scaffolded.

Na caixa de diálogo Adicionar Scaffolding:


Selecione Controlador MVC com exibições, usando o Entity Framework.
Clique em Adicionar. A caixa de diálogo Adicionar Controlador MVC com
exibições usando o Entity Framework é exibida:

Na classe Modelo, selecione Aluno.


Na classe de contexto Dados, selecione SchoolContext.
Aceite o StudentsController padrão como o nome.
Clique em Adicionar.

O mecanismo de scaffolding do Visual Studio cria um StudentsController.cs arquivo e


um conjunto de exibições ( *.cshtml arquivos) que funcionam com o controlador.

Observe que o controlador usa um SchoolContext como um parâmetro de construtor.


C#

namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;

public StudentsController(SchoolContext context)


{
_context = context;
}

A injeção de dependência do ASP.NET Core é responsável por passar uma instância de


SchoolContext para o controlador. Você configurou isso na Startup classe .

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:

C#

public async Task<IActionResult> Index()


{
return View(await _context.Students.ToListAsync());
}

Os elementos de programação assíncrona neste código são explicados posteriormente


no tutorial.

O Views/Students/Index.cshtml modo de exibição exibe esta lista em uma tabela:

CSHTML

@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 Students 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 o aplicativo é iniciado, o DbInitializer.Initialize método chama
EnsureCreated . O EF viu que não havia banco de dados:

Portanto, ele criou um banco de dados.


O Initialize código do método preencheu o banco de dados com dados.

Use SQL Server Pesquisador de Objetos (SSOX) para exibir o banco de dados no Visual
Studio:

Selecione SQL Server Pesquisador de Objetos no menu Exibir no Visual Studio.


Em SSOX, selecione (localdb)\BANCOs de dados MSSQLLocalDB>.
Selecione ContosoUniversity1 , a entrada para o nome do banco de dados que está
na cadeia de conexão no appsettings.json arquivo.
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
os dados na tabela.

Os *.mdf arquivos de banco de dados e *.ldf estão na pasta C:\Users\<username> .

Como EnsureCreated é chamado no método inicializador executado na inicialização do


aplicativo, você pode:

Faça uma alteração na Student classe .


Exclua o banco de dados.
Pare e inicie o aplicativo. O banco de dados é recriado automaticamente para
corresponder à alteração.

Por exemplo, se uma EmailAddress propriedade for adicionada à Student classe , uma
nova EmailAddress coluna na tabela recriada. A exibição não exibirá a nova
EmailAddress propriedade.

Convenções
A quantidade de código escrito para que o EF crie um banco de dados completo é
mínima devido ao uso das convenções que o EF usa:

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.
Propriedades de entidade nomeadas ID ou classnameID reconhecidas como
propriedades PK.
Uma propriedade será interpretada como uma propriedade FK se for nomeada
< nome da propriedade de navegação nome da propriedade >< > PK. Por exemplo,

para a Student propriedade de navegação, StudentID já que o Student PK da


entidade é ID . As propriedades FK também podem ser nomeadas nome < > da
propriedade da chave primária. Por exemplo, EnrollmentID como o Enrollment PK
da entidade é EnrollmentID .

O comportamento convencional pode ser substituído. Por exemplo, os nomes de tabela


podem ser especificados explicitamente, conforme mostrado anteriormente neste
tutorial. Os nomes de coluna e qualquer propriedade podem ser definidos como um PK
ou FK.

Código assíncrono
A programação assíncrona é o modo padrão para 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, async , Task<T> , await e ToListAsync faça com que o código seja
executado de forma assíncrona.

C#

public async Task<IActionResult> Index()


{
return View(await _context.Students.ToListAsync());
}

A palavra-chave async instrui o compilador a gerar retornos de chamada para


partes do corpo do método e a criar automaticamente o objeto
Task<IActionResult> que é retornado.

O tipo de retorno Task<IActionResult> representa um trabalho em andamento


com um resultado do tipo IActionResult .
A palavra-chave await faz com que o compilador divida o método em duas partes.
A primeira parte termina com a operação que é iniciada de forma assíncrona. A
segunda parte é colocada em um método de retorno de chamada que é chamado
quando a operação é concluída.
ToListAsync é a versão assíncrona do método de extensão ToList .

Algumas coisas a serem consideradas ao escrever código assíncrono que usa EF:

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 .
Para aproveitar os benefícios de desempenho do código assíncrono, verifique se
todos os pacotes de biblioteca usados também usam assíncrono se chamarem
métodos EF que fazem com que as consultas sejam enviadas para o 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.

Limitar entidades buscadas


Consulte Considerações sobre desempenho para obter informações sobre como limitar
o número de entidades retornadas de uma consulta.

Log SQL do Entity Framework Core


A configuração de log geralmente é fornecida pela seção Logging dos arquivos
appsettings.{Environment}.json . Para registrar instruções SQL em log, adicione

"Microsoft.EntityFrameworkCore.Database.Command": "Information" ao

appsettings.Development.json arquivo:

JSON

{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-
2;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"AllowedHosts": "*"
}

Com o ON anterior JS, as instruções SQL são exibidas na linha de comando e na janela
de saída do Visual Studio.

Para obter mais informações, consulte Log no .NET Core e ASP.NET Core e esse
problema do GitHub .
Vá para o próximo tutorial para aprender a executar operações CRUD (criar, ler, atualizar
e excluir) básicas.

Implementar a funcionalidade CRUD básica


Tutorial: Implementar a funcionalidade
CRUD – ASP.NET MVC com EF Core
Artigo • 28/11/2022 • 20 minutos para o fim da leitura

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.

7 Observação

É uma prática comum implementar o padrão de repositório para criar uma camada
de abstração entre o controlador e a camada de acesso a dados. Para manter esses
tutoriais simples e com foco no ensino de como usar o Entity Framework em si, eles
não usam repositórios. Para obter informações sobre repositórios com o EF,
consulte o último tutorial desta série.

Neste tutorial, você:

" Personalizar a página Detalhes


" Atualizar a página Criar
" Atualizar a página Editar
" Atualizar a página Excluir
" Fechará conexões de banco de dados

Pré-requisitos
Introdução e EF Core ASP.NET Core MVC

Personalizar a página Detalhes


O código gerado por scaffolding da página Índice de Alunos omitiu a propriedade
Enrollments , porque essa propriedade contém uma coleção. Na página Detalhes, você

exibirá o conteúdo da coleção em uma tabela HTML.

Em Controllers/StudentsController.cs , o método de ação para a exibição Detalhes usa


o FirstOrDefaultAsync método para recuperar uma única Student entidade. Adicione
um código que chama Include . ThenInclude AsNoTracking e métodos, conforme
mostrado no código realçado a seguir.

C#

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


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

var student = await _context.Students


.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

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

return View(student);
}

Os métodos Include e ThenInclude fazem com que o contexto carregue a propriedade


de navegação Student.Enrollments e, dentro de cada registro, a propriedade de
navegação Enrollment.Course . Você aprenderá mais sobre esses métodos no tutorial 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.

Rotear dados
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:

C#

app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});

Na URL a seguir, a rota padrão mapeia Instructor como o controlador, Index como a
ação e 1 como a ID; esses são valores de dados de rota.

http://localhost:1230/Instructor/Index/1?courseID=2021

A última parte da URL ("?courseID=2021") é um valor de cadeia de caracteres de


consulta. O associador de modelos passará o valor da ID para o parâmetro id do
método Index se você passá-lo como um valor de cadeia de caracteres de consulta:

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

Na página Índice, as URLs de hiperlink são criadas por instruções auxiliares de marca no
modo de exibição Razor . No código a seguir Razor , o id parâmetro corresponde à
rota padrão, portanto id , é adicionado aos dados de rota.

HTML

<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>

Isso gera o seguinte HTML quando item.ID é 6:

HTML

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


portanto, ele é adicionado como uma cadeia de caracteres de consulta.

HTML

<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>

Isso gera o seguinte HTML quando item.ID é 6:

HTML
<a href="/Students/Edit?studentID=6">Edit</a>

Para obter mais informações sobre auxiliares de marca, consulte Auxiliares de Marca no
ASP.NET Core.

Adicionar registros à exibição Detalhes


Abra o Views/Students/Details.cshtml . Cada campo é exibido usando auxiliares
DisplayNameFor e DisplayFor , conforme mostrado no seguinte exemplo:

CSHTML

<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd class="col-sm-10">
@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:

CSHTML

<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd class="col-sm-10">
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
Se o recuo do código estiver incorreto depois de colar o código, pressione CTRL-K-D
para corrigi-lo.

Esse código percorre as entidades na propriedade de navegação Enrollments . Para cada


registro, ele exibe o nome do curso e a nota. O título do curso é recuperado da entidade
Course, que é armazenada na propriedade de navegação Course da entidade
Enrollments.

Execute o aplicativo, selecione a guia Alunos e clique no link Detalhes de um aluno.


Você verá a lista de cursos e notas do aluno selecionado:

Atualizar a página Criar


Em StudentsController.cs , modifique o método HttpPost Create adicionando um
bloco try-catch e removendo a ID do Bind atributo.

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}

Esse código adiciona a entidade Student criada pelo associador de modelos do ASP.NET
Core MVC ao conjunto de entidades Student e, em seguida, salva as alterações no
banco de dados. (O associador de modelo refere-se à funcionalidade ASP.NET Core
MVC que facilita o trabalho com dados enviados por um formulário; um associador de
modelo converte valores de formulário postados em tipos CLR e os passa para o
método de ação em parâmetros. Nesse caso, o associador de modelo cria uma instância
de uma entidade Student para você usando valores de propriedade da coleção
Formulário.)

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, consulte Impedir ataques de

XSRF/CSRF (solicitação intersite forjada) no ASP.NET Core.


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.

C#

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 label input e span (para mensagens
de validação) auxiliares de marca 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 você a usar 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 .

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}

Altere a data para um valor válido e clique em Criar para ver o novo aluno ser exibido
na página Índice.

Atualizar a página Editar


Em StudentController.cs , o método HttpGet Edit (aquele sem o HttpPost atributo)
usa o FirstOrDefaultAsync método para recuperar a entidade Student selecionada,
como você viu no Details método. Não é necessário alterar esse método.

Código HttpPost Edit recomendado: ler e atualizar


Substitua o método de ação HttpPost Edit pelo código a seguir.

C#

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var studentToUpdate = await _context.Students.FirstOrDefaultAsync(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 prática recomendada para evitar a sobrepostagem, os campos que você
deseja atualizar pela página Editar são declarados nos TryUpdateModel parâmetros. (A
cadeia de caracteres vazia que precede a lista de campos na lista de parâmetros é um
prefixo a ser usado com os nomes dos campos de formulário.) Atualmente, não há
campos extras que você esteja protegendo, mas listar os campos que você deseja que o
associador de modelo associe garante que, se você adicionar campos ao modelo de
dados no futuro, eles serão protegidos automaticamente até que você os adicione
explicitamente aqui.

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

C#

public async Task<IActionResult> Edit(int id,


[Bind("ID,EnrollmentDate,FirstMidName,LastName")] Student student)
{
if (id != student.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(student);
}

Use essa abordagem quando a interface do usuário da página da Web incluir todos os
campos na entidade e puder atualizar qualquer um deles.

O código gerado por scaffolding usa a abordagem "criar e anexar", mas apenas captura
exceções DbUpdateConcurrencyException e retorna códigos de erro 404. O exemplo
mostrado captura qualquer exceção de atualização de banco de dados e exibe uma
mensagem de erro.

Estados da entidade
O contexto de banco de dados controla se as entidades em memória estão em sincronia
com suas linhas correspondentes no banco de dados, e essas informações determinam
o que acontece quando você chama o método SaveChanges . Por exemplo, quando você
passa uma nova entidade para o método Add , o estado dessa entidade é definido como
Added . Em seguida, quando você chama o método SaveChanges , o contexto de banco de

dados emite um comando SQL INSERT.

Uma entidade pode estar em um dos seguintes estados:

Added . A entidade ainda não existe no banco de dados. O método SaveChanges

emite uma instrução INSERT.

Unchanged . Nada precisa ser feito com essa entidade pelo método SaveChanges . Ao

ler uma entidade do banco de dados, a entidade começa com esse status.

Modified . Alguns ou todos os valores de propriedade da entidade foram


modificados. O método SaveChanges emite uma instrução UPDATE.

Deleted . A entidade foi marcada para exclusão. O método SaveChanges emite uma
instrução DELETE.

Detached . A entidade não está sendo controlada pelo contexto de banco de

dados.

Em um aplicativo da área de trabalho, em geral, as alterações de estado são definidas


automaticamente. Você lê uma entidade e faz alterações em alguns de seus valores de
propriedade. Isso faz com que seu estado da entidade seja alterado automaticamente
para Modified . Em seguida, quando você chama SaveChanges , o Entity Framework gera
uma instrução SQL UPDATE que atualiza apenas as propriedades reais que você alterou.

Em um aplicativo Web, o DbContext que inicialmente lê uma entidade e exibe seus


dados a serem editados é descartado depois que uma página é renderizada. Quando o
método de ação HttpPost Edit é chamado, uma nova solicitação da Web é feita e você
tem uma nova instância do DbContext . Se você ler novamente a entidade nesse novo
contexto, simulará o processamento da área de trabalho.

Mas se você não desejar fazer a operação de leitura extra, precisará usar o objeto de
entidade criado pelo associador de modelos. A maneira mais simples de fazer isso é
definir o estado da entidade como Modificado, como é feito no código HttpPost Edit
alternativo mostrado anteriormente. Em seguida, quando você chama SaveChanges , o
Entity Framework atualiza todas as colunas da linha de banco de dados, porque o
contexto não tem como saber quais propriedades foram alteradas.
Caso deseje evitar a abordagem de primeira leitura, mas também deseje que a instrução
SQL UPDATE atualize somente os campos que o usuário realmente alterar, o código será
mais complexo. É necessário salvar os valores originais de alguma forma (por exemplo,
usando campos ocultos) para que eles estejam disponíveis quando o método HttpPost
Edit for chamado. Em seguida, você pode criar uma entidade Student usando os

valores originais, chamar o método Attach com a versão original da entidade, atualizar
os valores da entidade para os novos valores e, em seguida, chamar SaveChanges .

Testar a página Editar


Execute o aplicativo, selecione a guia Alunos e, em seguida, clique em um hiperlink
Editar.

Altere alguns dos dados e clique em Salvar. A página Índice será aberta e você verá os
dados alterados.

Atualizar a página Excluir


Em StudentController.cs , o código de modelo para o método HttpGet Delete usa o
FirstOrDefaultAsync método para recuperar a entidade Student selecionada, como
você viu nos métodos Detalhes e Edição. No entanto, para implementar uma mensagem
de erro personalizada quando a chamada a SaveChanges falhar, você adicionará uma
funcionalidade a esse método e à sua exibição correspondente.

Como você viu para operações de atualização e criação, as operações de exclusão


exigem dois métodos de ação. O método chamado em resposta a uma solicitação GET
mostra uma exibição que dá ao usuário uma oportunidade de aprovar ou cancelar a
operação de exclusão. Se o usuário aprová-la, uma solicitação POST será criada. Quando
isso acontece, o método HttpPost Delete é chamado e, em seguida, esse método
executa, de fato, a operação de exclusão.

Você adicionará um bloco try-catch ao método HttpPost Delete para tratar os erros que
podem ocorrer quando o banco de dados é atualizado. Se ocorrer um erro, o método
HttpPost Delete chamará o método HttpGet Delete, passando a ele um parâmetro que
indica que ocorreu um erro. Em seguida, o método HttpGet Delete exibe novamente a
página de confirmação, junto com a mensagem de erro, dando ao usuário a
oportunidade de cancelar ou tentar novamente.

Substitua o método de ação HttpGet Delete pelo código a seguir, que gerencia o
relatório de erros.

C#

public async Task<IActionResult> Delete(int? id, bool? saveChangesError =


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

var student = await _context.Students


.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return NotFound();
}

if (saveChangesError.GetValueOrDefault())
{
ViewData["ErrorMessage"] =
"Delete failed. Try again, and if the problem persists " +
"see your system administrator.";
}
return View(student);
}

Este código aceita um parâmetro opcional que indica se o método foi chamado após
uma falha ao salvar as alterações. Esse parâmetro é falso quando o método HttpGet
Delete é chamado sem uma falha anterior. Quando ele é chamado pelo método

HttpPost Delete em resposta a um erro de atualização de banco de dados, o parâmetro


é verdadeiro, e uma mensagem de erro é passada para a exibição.

A abordagem de primeira leitura para HttpPost Delete


Substitua o método de ação HttpPost Delete (chamado DeleteConfirmed ) pelo código a
seguir, que executa a operação de exclusão real e captura os erros de atualização de
banco de dados.

C#

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var student = await _context.Students.FindAsync(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.)

C#

[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


In Views/Student/Delete.cshtml , add an error message between the h2 heading and the
h3 heading, as shown in the following example:

CSHTML

<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>

Execute o aplicativo, selecione a guia Alunos e, em seguida, clique em um hiperlink


Excluir:
Clique em Excluir. A página Índice será exibida sem o aluno excluído. (Você verá um
exemplo de código de tratamento de erro em ação no tutorial sobre simultaneidade.)

Fechará conexões de banco de dados


Para liberar os recursos contidos em uma conexão de banco de dados, a instância de
contexto precisa ser descartada assim que possível quando você tiver terminado. A
injeção de dependência interna do ASP.NET Core cuida dessa tarefa para você.

Em Startup.cs , você chama o método de extensão AddDbContext para provisionar a


DbContext classe no contêiner de DI ASP.NET Core. Esse método define o tempo de vida
do serviço como Scoped por padrão. Scoped significa que o tempo de vida do objeto de
contexto coincide com o tempo de vida da solicitação da Web, e o método Dispose será
chamado automaticamente ao final da solicitação da Web.

Lidar com transações


Por padrão, o Entity Framework implementa transações de forma implícita. Em cenários
em que são feitas alterações em várias linhas ou tabelas e, em seguida, SaveChanges é
chamado, o Entity Framework verifica automaticamente se todas as alterações tiveram
êxito ou se falharam. Se algumas alterações forem feitas pela primeira vez e, em
seguida, ocorrer um erro, essas alterações serão revertidas automaticamente. Para
cenários em que você precisa de mais controle – por exemplo, se desejar incluir
operações feitas fora do Entity Framework em uma transação, consulte Transações.

Consultas sem controle


Quando um contexto de banco de dados recupera linhas de tabela e cria objetos de
entidade que as representam, por padrão, ele controla se as entidades em memória
estão em sincronia com o que está no banco de dados. Os dados em memória atuam
como um cache e são usados quando uma entidade é atualizada. Esse cache costuma
ser desnecessário em um aplicativo Web porque as instâncias de contexto são
normalmente de curta duração (uma nova é criada e descartada para cada solicitação) e
o contexto que lê uma entidade normalmente é descartado antes que essa entidade
seja usada novamente.

Desabilite o controle de objetos de entidade em memória chamando o método


AsNoTracking . Os cenários típicos em que talvez você deseje fazer isso incluem os

seguintes:

Durante o tempo de vida do contexto, você não precisa atualizar nenhuma


entidade e não precisa de EF para carregar automaticamente as propriedades de
navegação com entidades recuperadas por consultas separadas. Com frequência,
essas condições são atendidas nos métodos de ação HttpGet de um controlador.

Você está executando uma consulta que recupera um volume grande de dados e
apenas uma pequena parte dos dados retornados será atualizada. Pode ser mais
eficiente desativar o controle para a consulta grande e executar uma consulta
posteriormente para as poucas entidades que precisam ser atualizadas.

Você deseja anexar uma entidade para atualizá-la, mas anteriormente, recuperou a
mesma entidade para uma finalidade diferente. Como a entidade já está sendo
controlada pelo contexto de banco de dados, não é possível anexar a entidade que
você deseja alterar. Uma maneira de lidar com essa situação é chamar
AsNoTracking na consulta anterior.

Para obter mais informações, consulte Acompanhamento versus Sem


Acompanhamento.
Obter o código
Baixe ou exiba o aplicativo concluído.

Próximas etapas
Neste tutorial, você:

" Personalizou a página Detalhes


" Atualizou a página Criar
" Atualizou a página Editar
" Atualizou a página Excluir
" Fechou conexões de banco de dados

Vá para o próximo tutorial para saber como expandir a funcionalidade da página Índice
adicionando classificação, filtragem e paginação.

Em seguida: Classificação, filtragem e paginação


Tutorial: Adicionar classificação,
filtragem e paginação – ASP.NET MVC
com EF Core
Artigo • 28/11/2022 • 15 minutos para o fim da leitura

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.

Neste tutorial, você:

" Adicionar links de classificação de coluna


" Adicionar uma caixa Pesquisa
" Adicionar paginação ao Índice de Alunos
" Adicionar paginação ao método Index
" Adicionar links de paginação
" Criar uma página Sobre

Pré-requisitos
Implementar funcionalidade CRUD

Adicionar links de classificação de coluna


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
In StudentsController.cs , substitua o Index método pelo seguinte código:

C#

public async Task<IActionResult> Index(string sortOrder)


{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc"
: "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
Esse código recebe um parâmetro sortOrder da cadeia de caracteres de consulta na
URL. O valor de cadeia de caracteres de consulta é fornecido pelo ASP.NET Core MVC
como um parâmetro para o método de ação. O parâmetro será uma cadeia de
caracteres "Name" ou "Date", opcionalmente, seguido de um sublinhado e a cadeia de
caracteres "desc" para especificar a ordem descendente. A ordem de classificação
padrão é crescente.

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.

C#

public async Task<IActionResult> Index(string sortOrder)


{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc"
: "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}

Essas são instruções ternárias. A primeira delas especifica que o parâmetro sortOrder é
nulo ou vazio, NameSortParm deve ser definido como "name_desc"; caso contrário, ele
deve ser definido como uma cadeia de caracteres vazia. Essas duas instruções permitem
que a exibição defina os hiperlinks de título de coluna da seguinte maneira:

Ordem de classificação atual Hiperlink do sobrenome Hiperlink de data

Sobrenome ascendente descending ascending

Sobrenome descendente ascending ascending

Data ascendente ascending descending

Data descendente ascending ascending

O método usa o LINQ to Entities para especificar a coluna pela qual classificar. O código
cria uma variável IQueryable antes da instrução switch, modifica-a na instrução switch e
chama o método ToListAsync após a instrução switch . Quando você cria e modifica
variáveis IQueryable , nenhuma consulta é enviada para o banco de dados. A consulta
não é executada até que você converta o objeto IQueryable em uma coleção chamando
um método, como ToListAsync . Portanto, esse código resulta em uma única consulta
que não é executada até a instrução return View .

Este código pode ficar detalhado com um grande número de colunas. O último tutorial
desta série mostra como escrever um código que permite que você passe o nome da
coluna OrderBy em uma variável de cadeia de caracteres.

Adicionar hiperlinks de título de coluna à exibição Índice


de Alunos
Substitua o código Views/Students/Index.cshtml por um código a seguir para adicionar
hiperlinks de título de coluna. As linhas alteradas são realçadas.

CSHTML

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


In StudentsController.cs , substitua o Index método pelo código a seguir (as alterações
são realçadas).

C#

public async Task<IActionResult> Index(string sortOrder, string


searchString)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc"
: "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
ViewData["CurrentFilter"] = searchString;

var students = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}

Você adicionou um parâmetro searchString ao método Index . O valor de cadeia de


caracteres de pesquisa é recebido em uma caixa de texto que você adicionará à exibição
Índice. Você também adicionou à instrução LINQ uma cláusula Where, que seleciona
somente os alunos cujo nome ou sobrenome contém a cadeia de caracteres de
pesquisa. A instrução que adiciona a cláusula Where é executada somente se há um
valor a ser pesquisado.

7 Observação

Aqui você está chamando o método Where em um objeto IQueryable , e o filtro


será processado no servidor. Em alguns cenários, você pode chamar o método
Where como um método de extensão em uma coleção em memória. (Por exemplo,

suponha que você altere a referência para _context.Students que, em vez de um


EF DbSet , ele faça referência a um método de repositório que retorna uma
IEnumerable coleção.) O resultado normalmente seria 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 ordenação da instância do SQL
Server. Por padrão, essa configuração diferencia maiúsculas de minúsculas. Você
pode chamar o ToUpper método para tornar o teste explicitamente sem
diferenciação de maiúsculas de minúsculas: Where(s => s.LastName.ToUpper().
Contains(searchString.ToUpper()). Isso garantirá que os resultados permaneçam
iguais se você alterar o código posteriormente para usar um repositório que
retorna uma IEnumerable coleção em vez de um IQueryable objeto. (Quando você
chama o Contains método em uma IEnumerable coleção, obtém o .NET Framework
implementação; quando você o chama em um IQueryable objeto, obtém a
implementação do provedor de banco de dados.) No entanto, há uma penalidade
de desempenho para essa solução. O código ToUpper colocará uma função na
cláusula WHERE da instrução TSQL SELECT. Isso pode impedir que o otimizador use
um índice. Considerando que o SQL geralmente é instalado como não
diferenciando maiúsculas e minúsculas, é melhor evitar o código ToUpper até você
migrar para um armazenamento de dados que diferencia maiúsculas de
minúsculas.

Adicionar uma Caixa de Pesquisa à exibição Índice de


Alunos
In Views/Student/Index.cshtml , adicione o código realçado imediatamente antes da
marca de abertura da tabela para criar uma legenda, uma caixa de texto e um botão
Pesquisar .

CSHTML

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

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


<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString"
value="@ViewData["CurrentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>

<table class="table">

Esse código usa o auxiliar de marcação <form> para adicionar o botão e a caixa de texto
de pesquisa. Por padrão, o auxiliar de marcação <form> envia dados de formulário com
um POST, o que significa que os parâmetros são passados no corpo da mensagem
HTTP e não na URL como cadeias de consulta. Quando você especifica HTTP GET, os
dados de formulário são passados na URL como cadeias de consulta, o que permite aos
usuários marcar a URL. As diretrizes do W3C recomendam o uso de GET quando a ação
não resulta em uma atualização.

Execute o aplicativo, selecione a guia Alunos, insira uma cadeia de caracteres de


pesquisa e clique em Pesquisar para verificar se a filtragem está funcionando.

Observe que a URL contém a cadeia de caracteres de pesquisa.

HTML

http://localhost:5813/Students?SearchString=an

Se você marcar essa página, obterá a lista filtrada quando usar o indicador. A adição de
method="get" à marcação form é o que fez com que a cadeia de caracteres de consulta
fosse gerada.

Neste estágio, se você clicar em um link de classificação de título de coluna perderá o


valor de filtro inserido na caixa Pesquisa. Você corrigirá isso na próxima seção.

Adicionar paginação ao Índice de Alunos


Para adicionar a paginação à página Índice de alunos, você criará uma classe
PaginatedList que usa as instruções Skip e Take para filtrar os dados no servidor, em
vez de recuperar sempre todas as linhas da tabela. Em seguida, você fará outras
alterações no método Index e adicionará botões de paginação à exibição Index . A
ilustração a seguir mostra os botões de paginação.

Na pasta do projeto, crie PaginatedList.cs e, em seguida, substitua o código de


modelo pelo código a seguir.

C#

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

namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int
pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);

this.AddRange(items);
}

public bool HasPreviousPage => PageIndex > 1;

public bool HasNextPage => PageIndex < TotalPages;

public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T>


source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) *
pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}

O método CreateAsync nesse código usa o tamanho da página e o número da página e


aplica as instruções Skip e Take ao IQueryable . Quando ToListAsync for chamado no
IQueryable , ele retornará uma Lista que contém somente a página solicitada. As
propriedades HasPreviousPage e HasNextPage podem ser usadas para habilitar ou
desabilitar os botões de paginação Anterior e Próximo.

Um método CreateAsync é usado em vez de um construtor para criar o objeto


PaginatedList<T> , porque os construtores não podem executar um código assíncrono.

Adicionar paginação ao método Index


In StudentsController.cs , substitua o Index método pelo código a seguir.

C#

public async Task<IActionResult> Index(


string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc"
: "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}

ViewData["CurrentFilter"] = searchString;

var students = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}

int pageSize = 3;
return View(await
PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 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.

C#

public async Task<IActionResult> Index(


string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
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.

C#

if (searchString != null)
{
pageNumber = 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.

C#

return View(await
PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1,
pageSize));

O método PaginatedList.CreateAsync usa um número de página. Os dois pontos de


interrogação representam o operador de união de nulo. O operador de união de nulo
define um valor padrão para um tipo que permite valor nulo; a expressão (pageNumber
?? 1) significa retornar o valor de pageNumber se ele tiver um valor ou retornar 1 se

pageNumber for nulo.

Adicionar links de paginação


In Views/Students/Index.cshtml , substitua o código existente pelo código a seguir. As
alterações são realçadas.

CSHTML

@model PaginatedList<ContosoUniversity.Models.Student>

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

<h2>Index</h2>

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

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


<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString"
value="@ViewData["CurrentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>

<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["NameSortParm"]" asp-route-
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-pageNumber="@(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-pageNumber="@(Model.PageIndex + 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @nextDisabled">
Next
</a>

A instrução @model na 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:
HTML

<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-


route-currentFilter ="@ViewData["CurrentFilter"]">Enrollment Date</a>

Os botões de paginação são exibidos por auxiliares de marcação:

HTML

<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>

Execute o aplicativo e acesse a página Alunos.

Clique nos links de paginação em ordens de classificação diferentes para verificar se a


paginação funciona. Em seguida, insira uma cadeia de caracteres de pesquisa e tente
fazer a paginação novamente para verificar se ela também funciona corretamente com a
classificação e filtragem.
Criar uma página Sobre
Para a página Sobre do site da Contoso University, você exibirá quantos alunos se
registraram para cada data de registro. Isso exige agrupamento e cálculos simples nos
grupos. Para fazer isso, você fará o seguinte:

Criar uma classe de modelo de exibição para os dados que você precisa passar
para a exibição.
Crie o método About no Home controlador.
Criar a exibição Sobre.

Criar o modelo de exibição


Crie uma pasta SchoolViewModels na pasta Models.

Na nova pasta, adicione um arquivo EnrollmentDateGroup.cs de classe e substitua o


código do modelo pelo seguinte código:

C#

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }

public int StudentCount { get; set; }


}
}

Modificar o Home controlador


In HomeController.cs , adicione o seguinte usando instruções na parte superior do
arquivo:

C#

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.Extensions.Logging;
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:

C#

public class HomeController : Controller


{
private readonly ILogger<HomeController> _logger;
private readonly SchoolContext _context;

public HomeController(ILogger<HomeController> logger, SchoolContext


context)
{
_logger = logger;
_context = context;
}

Adicione um método About com o seguinte código:

C#

public async Task<ActionResult> About()


{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
return View(await data.AsNoTracking().ToListAsync());
}

A instrução LINQ agrupa as entidades de alunos por data de registro, calcula o número
de entidades em cada grupo e armazena os resultados em uma coleção de objetos de
modelo de exibição EnrollmentDateGroup .

Criar a exibição Sobre


Adicione um Views/Home/About.cshtml arquivo com o seguinte código:

CSHTML

@model
IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>
@{
ViewData["Title"] = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>

@foreach (var item in Model)


{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>

Execute o aplicativo e acesse a página Sobre. A contagem de alunos para cada data de
registro é exibida em uma tabela.

Obter o código
Baixe ou exiba o aplicativo concluído.

Próximas etapas
Neste tutorial, você:

" Adicionou links de classificação de coluna


" Adicionou uma caixa Pesquisa
" Adicionou paginação ao Índice de Alunos
" Adicionou paginação ao método Index
" Adicionou links de paginação
" Criou uma página Sobre
Vá para o próximo tutorial para aprender a manipular as alterações do modelo de dados
usando migrações.

Próximo: Manipular alterações no modelo de dados


Tutorial: Parte 5, aplicar migrações ao
exemplo da Contoso University
Artigo • 28/11/2022 • 6 minutos para o fim da leitura

Neste tutorial, você começa a usar o EF Core recurso de migrações para gerenciar
alterações de modelo de dados. Em tutoriais seguintes, você adicionará mais migrações
conforme você alterar o modelo de dados.

Neste tutorial, você:

" Aprenderá sobre migrações


" Criar uma migração inicial
" Examinará os métodos Up e Down
" Aprenderá sobre o instantâneo do modelo de dados
" Aplicar a migração

Pré-requisitos
Classificação, filtragem e paginação

Sobre 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 EF Core recurso Migrações resolve esse problema permitindo que o EF
atualize o esquema de banco de dados em vez de criar um novo banco de dados.

Para trabalhar com migrações, você pode usar o PMC ( Console do Gerenciador de
Pacotes ) ou a CLI. Esses tutoriais mostram como usar comandos da CLI. Encontre
informações sobre o PMC no final deste tutorial.
Remover o banco de dados
Instale EF Core ferramentas como uma ferramenta global e exclua o banco de dados:

CLI do .NET

dotnet tool install --global dotnet-ef


dotnet ef database drop

A seção a seguir explica como executar comandos da CLI.

Criar uma migração inicial


Salve as alterações e compile o projeto. Em seguida, abra uma janela Comando e
navegue para a pasta do projeto. Esta é uma maneira rápida de fazer isso:

No Gerenciador de Soluções, clique com o botão direito do mouse no projeto e


escolha Abrir Pasta no Explorador de Arquivos no menu de contexto.

Insira "cmd" na barra de endereços e pressione Enter.


Insira o seguinte comando na janela de comando:

CLI do .NET

dotnet ef migrations add InitialCreate

Nos comandos anteriores, uma saída semelhante à seguinte é exibida:

Console

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Done. To undo this action, use 'ef migrations remove'

Se você vir uma mensagem de erro "não é possível acessar o arquivo ...
ContosoUniversity.dll porque ele está sendo usado por outro processo.", localize o ícone IIS
Express na Bandeja do Sistema do Windows e clique com o botão direito do mouse nele
e, em seguida, clique em ContosoUniversity > Stop Site.

Examinará os métodos Up e Down


Quando você executou o comando migrations add , o EF gerou o código que criará o
banco de dados do zero. Esse código está na pasta Migrações , no arquivo chamado
<timestamp>_InitialCreate.cs . O método Up da classe InitialCreate cria as tabelas de

banco de dados que correspondem aos conjuntos de entidades do modelo de dados, e


o método Down exclui-as, conforme mostrado no exemplo a seguir.
C#

public partial class InitialCreate : Migration


{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Course",
columns: table => new
{
CourseID = table.Column<int>(nullable: false),
Credits = table.Column<int>(nullable: false),
Title = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Course", x => x.CourseID);
});

// Additional code not shown


}

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(
name: "Enrollment");
// Additional code not shown
}
}

As migrações chamam o método Up para implementar as alterações do modelo de


dados para uma migração. Quando você insere um comando para reverter a
atualização, as Migrações chamam o método Down .

Esse código destina-se à migração inicial que foi criada quando você inseriu o comando
migrations add InitialCreate . O parâmetro de nome da migração ("InitialCreate" no
exemplo) é usado para o nome do arquivo e pode ser o que você desejar. É melhor
escolher uma palavra ou frase que resume o que está sendo feito na migração. Por
exemplo, você pode nomear uma migração posterior "AddDepartmentTable".

Se você criou a migração inicial quando o banco de dados já existia, o código de criação
de banco de dados é gerado, mas ele não precisa ser executado porque o banco de
dados já corresponde ao modelo de dados. Quando você implantar o aplicativo em
outro ambiente no qual o banco de dados ainda não existe, esse código será executado
para criar o banco de dados; portanto, é uma boa ideia testá-lo primeiro. É por isso que
você retirou o banco de dados 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.

Use as migrações de ef dotnet para remover o comando para remover uma migração.
dotnet ef migrations remove exclui a migração e garante que o instantâneo seja

redefinido corretamente. Se dotnet ef migrations remove falhar, use dotnet ef


migrations remove -v para obter mais informações sobre a falha.

Consulte EF Core Migrações em Ambientes de Equipe para obter mais informações


sobre como o arquivo de instantâneo é usado.

Aplicar a migração
Na janela Comando, insira o comando a seguir para criar o banco de dados e tabelas
nele.

CLI do .NET

dotnet ef database update

A saída do comando é semelhante ao comando migrations add , exceto que os logs


para os comandos SQL que configuram o banco de dados são exibidos. A maioria dos
logs é omitida na seguinte saída de exemplo. Se você preferir não ver esse nível de
detalhes em mensagens de log, poderá alterar o nível de log no
appsettings.Development.json arquivo. Para obter mais informações, confira Registro

em log no .NET Core e no ASP.NET Core.

text

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (274ms) [Parameters=[], CommandType='Text',
CommandTimeout='60']
CREATE DATABASE [ContosoUniversity2];
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (60ms) [Parameters=[], CommandType='Text',
CommandTimeout='60']
IF SERVERPROPERTY('EngineEdition') <> 5
BEGIN
ALTER DATABASE [ContosoUniversity2] SET READ_COMMITTED_SNAPSHOT
ON;
END;
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (15ms) [Parameters=[], CommandType='Text',
CommandTimeout='30']
CREATE TABLE [__EFMigrationsHistory] (
[MigrationId] nvarchar(150) NOT NULL,
[ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);

<logs omitted for brevity>

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text',
CommandTimeout='30']
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20190327172701_InitialCreate', N'5.0-rtm');
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.


Comparar CLI e PMC
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 o que você instala para a CLI editando o
.csproj arquivo. 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).
Obter o código
Baixe ou exiba o aplicativo concluído.

Próxima etapa
Vá para o próximo tutorial para começar a examinar tópicos mais avançados sobre a
expansão do modelo de dados. Ao longo do processo, você criará e aplicará migrações
adicionais.

Criar e aplicar migrações adicionais


Tutorial: Criar um modelo de dados
complexo – ASP.NET MVC com EF Core
Artigo • 28/11/2022 • 32 minutos para o fim da leitura

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:
Neste tutorial, você:

" Personalizar o Modelo de dados


" Fazer alterações na entidade Student
" Criar a entidade Instructor
" Criar a entidade OfficeAssignment
" Modificar a entidade Course
" Criar a entidade Department
" Modificar a entidade Enrollment
" Atualizar o contexto de banco de dados
" Propagar o banco de dados com dados de teste
" Adicionar uma migração
" Alterar a cadeia de conexão
" Atualizar o banco de dados

Pré-requisitos
Usando EF Core migrações

Personalizar o Modelo de dados


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 .

In Models/Student.cs , add a using statement for the


System.ComponentModel.DataAnnotations namespace and add DataType and

DisplayFormat attributes to the EnrollmentDate property, as shown in the following


example:

C#

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

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

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


}
}

O atributo DataType é usado para especificar um tipo de dados mais específico do que
o tipo intrínseco de banco de dados. Nesse caso, apenas desejamos acompanhar a data,
não a data e a hora. A Enumeração DataType fornece muitos tipos de dados, como Date,
Time, PhoneNumber, Currency, EmailAddress e muito mais. O atributo DataType
também pode permitir que o aplicativo forneça automaticamente recursos específicos a
um tipo. Por exemplo, um link mailto: pode ser criado para DataType.EmailAddress e
um seletor de data pode ser fornecido para DataType.Date em navegadores que dão
suporte a HTML5. O atributo DataType emite atributos data- HTML 5 (pronunciados
“data dash”) que são reconhecidos pelos navegadores HTML 5. Os atributos DataType
não fornecem nenhuma validação.

DataType.Date não especifica o formato da data exibida. Por padrão, o campo de dados
é exibido de acordo com os formatos padrão com base nas CultureInfo do servidor.

O atributo DisplayFormat é usado para especificar explicitamente o formato de data:

C#

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


true)]

A configuração ApplyFormatInEditMode especifica que a formatação também deve ser


aplicada quando o valor é exibido em uma caixa de texto para edição. (Talvez você não
deseje ter isso em alguns campos – por exemplo, para valores de moeda,
provavelmente, você não deseja exibir o símbolo de moeda na caixa de texto para
edição.)

Use Você pode usar o atributo DisplayFormat por si só, mas geralmente é uma boa ideia
usar o atributo DataType também. O atributo DataType transmite a semântica dos
dados, ao invés de apresentar como renderizá-lo em uma tela, e oferece os seguintes
benefícios que você não obtém com DisplayFormat :

O navegador pode habilitar os recursos do HTML5 (por exemplo, mostrar um


controle de calendário, o símbolo de moeda apropriado à localidade, links de
email, uma validação de entrada do lado do cliente, etc.).

Por padrão, o navegador renderizará os dados usando o formato correto de


acordo com a localidade.

Para obter mais informações, consulte a documentação do auxiliar de< marca de


entrada>.

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 StringLength atributo define o comprimento máximo no
banco de dados e fornece validação do lado do cliente e do servidor para 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:

C#

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)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

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


}
}

O atributo StringLength não impedirá que um usuário insira um espaço em branco em


um nome. Use o atributo RegularExpression para aplicar restrições à entrada. Por
exemplo, o seguinte código exige que o primeiro caractere esteja em maiúscula e os
caracteres restantes estejam em ordem alfabética:

C#

[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]

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:
CLI do .NET

dotnet ef migrations add MaxLengthOnNames

CLI do .NET

dotnet ef database update

O comando migrations add alerta que pode ocorrer perda de dados, pois a alteração
torna o tamanho máximo mais curto para duas colunas. As migrações criam um arquivo
chamado <timeStamp>_MaxLengthOnNames.cs . Esse arquivo contém o código no método
Up que atualizará o banco de dados para que ele corresponda ao modelo de dados
atual. O comando database update executou esse código.

O carimbo de data/hora prefixado ao nome do arquivo de migrações é usado pelo


Entity Framework para ordenar as migrações. Crie várias migrações antes de executar o
comando de atualização de banco de dados e, em seguida, todas as migrações são
aplicadas na ordem em que foram criadas.

Execute o aplicativo, selecione a guia Alunos, clique em Criar Novo e tente inserir um
nome com mais de 50 caracteres. O aplicativo deve impedir isso.

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.

Student.cs No arquivo, adicione uma using instrução e

System.ComponentModel.DataAnnotations.Schema adicione o atributo de nome da


FirstMidName coluna à propriedade, conforme mostrado no seguinte código realçado:
C#

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)]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

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


}
}

A adição do atributo Column altera o modelo que dá suporte ao SchoolContext e,


portanto, ele não corresponde ao banco de dados.

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


projeto e insira os seguintes comandos para criar outra migração:

CLI do .NET

dotnet ef migrations add ColumnFirstName

CLI do .NET

dotnet ef database update

No Pesquisador de Objetos do SQL Server, abra o designer de tabela Aluno clicando


duas vezes na tabela Aluno.
Antes de você aplicar as duas primeiras migrações, as colunas de nome eram do tipo
nvarchar(MAX). Agora elas são nvarchar(50) e o nome da coluna foi alterado de
FirstMidName para FirstName.

7 Observação

Se você tentar compilar antes de concluir a criação de todas as classes de entidade


nas seções a seguir, poderá receber erros do compilador.

Alterações na entidade Student

In Models/Student.cs , substitua o código que você adicionou anteriormente pelo


código a seguir. As alterações são realçadas.

C#
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)]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}

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


}
}

O atributo Required
O atributo Required torna as propriedades de nome campos obrigatórios. O atributo
Required não é necessário para tipos que permitem valor nulo como tipos de valor

(DateTime, int, double, float, etc.). Tipos que não podem ser nulos são tratados
automaticamente como campos obrigatórios.

O atributo Required precisa ser usado com MinimumLength para que MinimumLength seja
imposto.

C#
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

O atributo Display
O atributo Display especifica que a legenda para as caixas de texto deve ser "Nome",
"Sobrenome", "Nome Completo" e "Data de Registro", em vez do nome de a
propriedade em cada instância (que não tem nenhum espaço entre as palavras).

A propriedade calculada FullName


FullName é uma propriedade calculada que retorna um valor criado pela concatenação

de duas outras propriedades. Portanto, ela tem apenas um acessador get e nenhuma
coluna FullName será gerada no banco de dados.

Criar a entidade Instructor

Crie Models/Instructor.cs , substituindo o código do modelo pelo seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }

[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }

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

[Display(Name = "Full Name")]


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

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


public OfficeAssignment OfficeAssignment { get; set; }
}
}

Observe que várias propriedades são as mesmas nas entidades Student e Instructor. No
tutorial Implementando a herança mais adiante nesta série, você refatorará esse código
para eliminar a redundância.

Coloque vários atributos em uma linha, de modo que você também possa escrever os
atributos HireDate da seguinte maneira:

C#

[DataType(DataType.Date),Display(Name = "Hire
Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]

As propriedades de navegação CourseAssignments e


OfficeAssignment
As propriedades CourseAssignments e OfficeAssignment são propriedades de
navegação.
Um instrutor pode ministrar qualquer quantidade de cursos e, portanto,
CourseAssignments é definido como uma coleção.

C#

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

Se uma propriedade de navegação pode conter várias entidades, o tipo precisa ser uma
lista na qual as entradas podem ser adicionadas, excluídas e atualizadas. Especifique
ICollection<T> ou um tipo, como List<T> ou HashSet<T> . Se você especificar

ICollection<T> , o EF criará uma coleção HashSet<T> por padrão.

O motivo pelo qual elas são entidades CourseAssignment é explicado abaixo na seção
sobre relações muitos para muitos.

As regras de negócio do Contoso Universidade indicam que um instrutor pode ter


apenas, no máximo, um escritório; portanto, a propriedade OfficeAssignment contém
uma única entidade OfficeAssignment (que pode ser nulo se o escritório não está
atribuído).

C#

public OfficeAssignment OfficeAssignment { get; set; }

Criar a entidade OfficeAssignment

Crie Models/OfficeAssignment.cs com o seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }

public Instructor Instructor { get; set; }


}
}

O atributo Key
Há uma relação um-para-zero-ou-um entre as entidades e as
Instructor OfficeAssignment entidades. Uma atribuição de escritório só existe em

relação ao instrutor ao qual foi atribuída e, portanto, sua chave primária também é sua
chave estrangeira para a Instructor entidade. No entanto, o Entity Framework não
pode reconhecer InstructorID automaticamente como a chave primária dessa entidade
porque seu nome não segue a ID convenção de nomenclatura. classnameID Portanto, o
atributo Key é usado para identificá-la como a chave:

C#

[Key]
public int InstructorID { get; set; }

Você também pode usar o atributo Key se a entidade tem sua própria chave primária,
mas você deseja atribuir um nome de propriedade que não seja classnameID ou ID.

Por padrão, o EF trata a chave como não gerada pelo banco de dados porque a coluna
destina-se a uma relação de identificação.

A propriedade de navegação Instructor


A entidade Instructor tem uma propriedade de navegação OfficeAssignment que
permite valor nulo (porque um instrutor pode não ter uma atribuição de escritório), e a
entidade OfficeAssignment tem uma propriedade de navegação Instructor que não
permite valor nulo (porque uma atribuição de escritório não pode existir sem um
instrutor – InstructorID não permite valor nulo). Quando uma entidade Instructor tiver
uma entidade OfficeAssignment relacionada, cada entidade terá uma referência à outra
em sua propriedade de navegação.
Você pode colocar um atributo [Required] na propriedade de navegação Instructor
para especificar que deve haver um instrutor relacionado, mas não precisa fazer isso
porque a chave estrangeira InstructorID (que também é a chave para esta tabela) não
permite valor nulo.

Modificar a entidade Course

In Models/Course.cs , substitua o código que você adicionou anteriormente pelo código


a seguir. As alterações são realçadas.

C#

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

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Title { get; set; }

[Range(0, 5)]
public int Credits { get; set; }

public int DepartmentID { get; set; }

public Department Department { get; set; }


public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
}
A entidade de curso tem uma propriedade de chave estrangeira DepartmentID que
aponta para a entidade Department relacionada e ela tem uma propriedade de
navegação Department .

O Entity Framework não exige que você adicione uma propriedade de chave estrangeira
ao modelo de dados quando você tem uma propriedade de navegação para uma
entidade relacionada. O EF cria chaves estrangeiras no banco de dados sempre que elas
são necessárias e cria automaticamente propriedades de sombra para elas. No entanto,
ter a chave estrangeira no modelo de dados pode tornar as atualizações mais simples e
mais eficientes. Por exemplo, quando você busca uma Course entidade para editar, a
Department entidade é nula se você não carregá-la, portanto, ao atualizar a Course
entidade, você terá que buscar primeiro a Department entidade. Quando a propriedade
DepartmentID de chave estrangeira é incluída no modelo de dados, você não precisa
buscar a Department entidade antes de atualizar.

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.

C#

[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 Course entidades, você usará um número de curso especificado pelo
usuário, como uma série 1000 para 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 chave estrangeira e as propriedades de navegação na Course
entidade 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.

C#

public int DepartmentID { get; set; }


public Department Department { get; set; }

Um curso pode ter qualquer quantidade de estudantes inscritos; portanto, a


propriedade de navegação Enrollments é uma coleção:

C#

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

Um curso pode ser ministrado por vários instrutores; portanto, a propriedade de


navegação CourseAssignments é uma coleção (o tipo CourseAssignment é explicado
posteriormente):

C#

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

Criar a entidade Department

Crie Models/Department.cs com o seguinte código:

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

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

O atributo Column
Anteriormente, você usou o atributo Column para alterar o mapeamento de nome de
coluna. No código da Department entidade, o Column atributo está sendo usado para
alterar o mapeamento de tipo de dados SQL para que a coluna seja definida usando o
tipo SQL Server money no banco de dados:

C#

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

C#

public int? InstructorID { get; set; }


public Instructor Administrator { get; set; }

Um departamento pode ter vários cursos e, portanto, há uma propriedade de


navegação Courses:

C#

public ICollection<Course> Courses { get; set; }

7 Observação

Por convenção, o Entity Framework habilita a exclusão em cascata para chaves


estrangeiras que não permitem valor nulo e em relações muitos para muitos. Isso
pode resultar em regras de exclusão em cascata circular, que causará uma exceção
quando você tentar adicionar uma migração. Por exemplo, se você não definisse a
Department.InstructorID propriedade como anulável, o EF configuraria uma regra

de exclusão em cascata para excluir o departamento quando você excluir o


instrutor, o 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:

C#
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)

Modificar a entidade Enrollment

In Models/Enrollment.cs , substitua o código que você adicionou anteriormente pelo


seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}
Propriedades de navegação e de chave estrangeira
As propriedades de navegação e de chave estrangeira refletem as seguintes relações:

Um registro destina-se a um único curso e, portanto, há uma propriedade de chave


estrangeira CourseID e uma propriedade de navegação Course :

C#

public int CourseID { get; set; }


public Course Course { get; set; }

Um registro destina-se a um único aluno e, portanto, há uma propriedade de chave


estrangeira StudentID e uma propriedade de navegação Student :

C#

public int StudentID { get; set; }


public Student Student { get; set; }

Relações muitos para muitos


Há uma relação muitos para muitos entre entidades e Course entidades Student e a
Enrollment entidade funciona como uma tabela de junção muitos para muitos com
conteúdo no banco de dados. "Com conteúdo" significa que a Enrollment tabela
contém dados adicionais além de chaves estrangeiras para as tabelas unidas (nesse
caso, uma chave primária e uma Grade propriedade).

A ilustração a seguir mostra a aparência dessas relações em um diagrama de entidades.


(Esse diagrama foi gerado usando o Entity Framework Power Tools para o EF 6.x; a
criação do diagrama não faz parte do tutorial, mas está apenas sendo usada aqui como
uma ilustração.)
Cada linha de relação tem um 1 em uma extremidade e um asterisco (*) na outra,
indicando uma relação um para muitos.

Se a Enrollment tabela não incluísse informações de nota, ela só precisaria 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 Instructor entidades e Course entidades têm esse tipo de relação muitos
para muitos, e sua próxima etapa é criar uma classe de entidade para funcionar como
uma tabela de junção sem carga.

EF Core dá suporte a tabelas de junção implícitas para relações muitos para muitos, mas
essa tutoria não foi atualizada para usar uma tabela de junção implícita. Consulte
Relações Muitos para Muitos, a Razor versão de Páginas deste tutorial que foi
atualizada.

A entidade CourseAssignment
Crie Models/CourseAssignment.cs com o seguinte código:

C#

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

namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}

Unir nomes de entidade


Uma tabela de junção é necessária no banco de dados para a relação muitos para
muitos de Instrutor para Cursos, e ela precisa ser representada por um conjunto de
entidades. É comum nomear uma entidade de junção EntityName1EntityName2 , que,
nesse caso, será CourseInstructor . No entanto, recomendamos que você escolha um
nome que descreve a relação. Modelos de dados começam simples e aumentam, com
junções não referentes a conteúdo obtendo com frequência o conteúdo mais tarde. Se
você começar com um nome descritivo de entidade, não precisará alterar o nome
posteriormente. O ideal é que a entidade de junção tenha seu próprio nome natural
(possivelmente, uma única palavra) no domínio de negócios. Por exemplo, Manuais e
Clientes podem ser vinculados por meio de Classificações. Para essa relação,
CourseAssignment é uma escolha melhor que CourseInstructor .

Chave composta
Como as chaves estrangeiras não permitem valor nulo e, juntas, identificam
exclusivamente cada linha da tabela, não é necessário ter uma chave primária. As
InstructorID propriedades e CourseID as propriedades devem funcionar como uma

chave primária composta. A única maneira de identificar chaves primárias compostas no


EF é usando a API fluente (isso não pode ser feito por meio de atributos). Você verá
como configurar a chave primária composta na próxima seção.

A chave composta garante que, embora você possa ter várias linhas para um curso e
várias linhas para um instrutor, não poderá ter várias linhas para o mesmo instrutor e
curso. A entidade de junção Enrollment define sua própria chave primária e, portanto,
duplicatas desse tipo são possíveis. Para evitar essas duplicatas, você pode adicionar um
índice exclusivo nos campos de chave estrangeira ou configurar Enrollment com uma
chave primária composta semelhante a CourseAssignment . Para obter mais informações,
consulte Índices.

Atualizar o contexto de banco de dados


Adicione o seguinte código realçado ao Data/SchoolContext.cs arquivo:

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>
().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>
().ToTable("CourseAssignment");

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

Esse código adiciona novas entidades e configura a chave primária composta da


entidade CourseAssignment.

Sobre a alternativa de API fluente


O código no método OnModelCreating da classe DbContext usa a API fluente para
configurar o comportamento do EF. A API é chamada de "fluente" porque geralmente é
usada pela cadeia de caracteres de uma série de chamadas de método em uma única
instrução, como neste exemplo da EF Core documentação:

C#

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}

Neste tutorial, você usa a API fluente somente para o mapeamento de banco de dados
que não é possível fazer com atributos. No entanto, você também pode usar a API
fluente para especificar a maioria das regras de formatação, validação e mapeamento
que pode ser feita por meio de atributos. Alguns atributos como MinimumLength não
podem ser aplicados com a API fluente. Conforme mencionado anteriormente,
MinimumLength não altera o esquema; apenas aplica uma regra de validação do lado do
cliente e do servidor.

Alguns desenvolvedores preferem usar a API fluente exclusivamente para que possam
manter suas classes de entidade "limpas". Você pode misturar atributos e API fluente, se
desejar, e há algumas personalizações que só podem ser feitas usando a API fluente,
mas, em geral, a prática recomendada é escolher uma dessas duas abordagens e usá-las
consistentemente o máximo possível. Se usar as duas, observe que sempre que houver
um conflito, a API fluente substituirá atributos.

Para obter mais informações sobre atributos vs. API fluente, consulte Métodos de
configuração.

Diagrama de entidade mostrando relações


A ilustração a seguir mostra o diagrama criado pelo Entity Framework Power Tools para
o modelo Escola concluído.
Além das linhas de relação um para muitos (1 para *), você pode ver aqui a linha de
relação um para zero ou um (1 a 0..1) entre entidades Instructor e OfficeAssignment
entidades e a linha de relacionamento zero ou um para muitos (0,1 a *) entre as
entidades Instrutor e Departamento.

Propagar o banco de dados com dados de


teste
Substitua o código no Data/DbInitializer.cs arquivo pelo código a seguir para
fornecer dados de semente para as novas entidades que você criou.

C#

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();

// Look for any students.


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

var students = new Student[]


{
new Student { FirstMidName = "Carson", LastName =
"Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName =
"Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName =
"Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName =
"Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName =
"Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName =
"Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};

foreach (Student s in students)


{
context.Students.Add(s);
}
context.SaveChanges();

var instructors = new Instructor[]


{
new Instructor { FirstMidName = "Kim", LastName =
"Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName =
"Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName =
"Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName =
"Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName =
"Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};

foreach (Instructor i in instructors)


{
context.Instructors.Add(i);
}
context.SaveChanges();

var departments = new Department[]


{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName ==
"Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName ==
"Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName ==
"Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName ==
"Kapoor").ID }
};

foreach (Department d in departments)


{
context.Departments.Add(d);
}
context.SaveChanges();

var courses = new Course[]


{
new Course {CourseID = 1050, Title = "Chemistry",
Credits = 3,
DepartmentID = departments.Single( s => s.Name ==
"Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics",
Credits = 3,
DepartmentID = departments.Single( s => s.Name ==
"Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics",
Credits = 3,
DepartmentID = departments.Single( s => s.Name ==
"Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus",
Credits = 4,
DepartmentID = departments.Single( s => s.Name ==
"Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry",
Credits = 4,
DepartmentID = departments.Single( s => s.Name ==
"Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition",
Credits = 3,
DepartmentID = departments.Single( s => s.Name ==
"English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature",
Credits = 4,
DepartmentID = departments.Single( s => s.Name ==
"English").DepartmentID
},
};

foreach (Course c in courses)


{
context.Courses.Add(c);
}
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName ==
"Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName ==
"Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName ==
"Kapoor").ID,
Location = "Thompson 304" },
};

foreach (OfficeAssignment o in officeAssignments)


{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();

var courseInstructors = new CourseAssignment[]


{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title ==
"Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title ==
"Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Abercrombie").ID
},
};

foreach (CourseAssignment ci in courseInstructors)


{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry"
).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alexander").ID,
CourseID = courses.Single(c => c.Title ==
"Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alexander").ID,
CourseID = courses.Single(c => c.Title ==
"Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus"
).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry"
).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition"
).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry"
).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Anand").ID,
CourseID = courses.Single(c => c.Title ==
"Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Barzdukas").ID,
CourseID = courses.Single(c => c.Title ==
"Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title ==
"Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Justice").ID,
CourseID = courses.Single(c => c.Title ==
"Literature").CourseID,
Grade = Grade.B
}
};

foreach (Enrollment e in enrollments)


{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID ==
e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
}
}

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

Adicionar uma migração


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

CLI do .NET

dotnet ef migrations add ComplexDataModel

Você receberá um aviso sobre a possível perda de dados.

text

An operation was scaffolded that may result in the loss of data. Please
review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'

Se você tiver tentado executar o comando database update neste ponto (não faça isso
ainda), receberá o seguinte erro:

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


"FK_dbo.Course_dbo.Department_DepartmentID". O conflito ocorreu no banco de
dados "ContosoUniversity", tabela "dbo.Departamento", coluna 'DepartmentID'.
Às vezes, quando você executa migrações com os dados existentes, precisa inserir
dados de stub no banco de dados para atender às restrições de chave estrangeira. O
código gerado no Up método adiciona uma chave estrangeira não anulável
DepartmentID à Course tabela. 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.

C#

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:

C#

migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(nullable: true),
Name = table.Column<string>(maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Department", x => x.DepartmentID);
table.ForeignKey(
name: "FK_Department_Instructor_InstructorID",
column: x => x.InstructorID,
principalTable: "Instructor",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});

migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget,


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

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

Em um aplicativo de produção, você escreverá código ou scripts para adicionar linhas


Departamento e relacionar linhas Curso às novas linhas Departamento. Em seguida,
você não precisaria mais do departamento "Temporário" ou do valor padrão na
Course.DepartmentID coluna.

Salve as alterações e compile o projeto.

Alterar a cadeia de conexão


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

JSON

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

Salve sua alteração em appsettings.json .

7 Observação

Como alternativa à alteração do nome do banco de dados, você pode excluir o


banco de dados. Use o SSOX (Pesquisador de Objetos do SQL Server) ou o
comando database drop da CLI:

CLI do .NET

dotnet ef database drop

Atualizar o banco de dados


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.

CLI do .NET

dotnet ef database update

Execute o aplicativo para fazer com que o método DbInitializer.Initialize execute e


popule o novo banco de dados.

Abra o banco de dados no SSOX, como você fez anteriormente, e expanda o nó Tabelas
para ver se todas as tabelas foram criadas. (Se você ainda tem o SSOX aberto do
momento anterior, clique no botão Atualizar.)
Execute o aplicativo para disparar o código inicializador que propaga o banco de dados.

Clique com o botão direito do mouse na tabela CourseAssignment e selecione Exibir


Dados para verificar se existem dados nela.

Obter o código
Baixe ou exiba o aplicativo concluído.

Próximas etapas
Neste tutorial, você:
" Personalizou o Modelo de dados
" Fez alterações na entidade Student
" Criou a entidade Instructor
" Criou a entidade OfficeAssignment
" Modificou a entidade Course
" Criou a entidade Department
" Modificou a entidade Enrollment
" Atualizou o contexto de banco de dados
" Propagou o banco de dados com os dados de teste
" Adicionou uma migração
" Alterou a cadeia de conexão
" Atualizou o banco de dados

Vá para o próximo tutorial para aprender a acessar dados relacionados.

Próximo: Acessar dados relacionados


Tutorial: Ler dados relacionados –
ASP.NET MVC com EF Core
Artigo • 28/11/2022 • 15 minutos para o fim da leitura

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


Neste tutorial, você:

" Aprender a carregar entidades relacionadas


" Criar uma página Cursos
" Criar uma página Instrutores
" Aprender sobre o carregamento explícito
Pré-requisitos
Criar um modelo de dados complexo

Aprender a carregar entidades relacionadas


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 ansioso: Quando a entidade é lida, os dados relacionados são


recuperados junto com ela. Normalmente, isso resulta em uma única consulta de
junção que recupera todos os dados necessários. Especifique o carregamento
adiantado no Entity Framework Core usando os métodos Include e ThenInclude .

Recupere alguns dos dados em consultas separadas e o EF "corrigirá" as


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

Carregamento explícito: Quando a entidade é lida pela primeira vez, os dados


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

Carregamento lento: Quando a entidade é lida pela primeira vez, os dados


relacionados não são recuperados. No entanto, na primeira vez que você tenta
acessar uma propriedade de navegação, os dados necessários para essa
propriedade de navegação são recuperados automaticamente. Uma consulta é
enviada ao banco de dados sempre que você tenta obter dados de uma
propriedade de navegação pela primeira vez. O Entity Framework Core 1.0 não dá
suporte ao carregamento lento.

Considerações sobre o desempenho


Se você sabe que precisa de dados relacionados para cada entidade recuperada, o
carregamento adiantado costuma oferecer o melhor desempenho, porque uma única
consulta enviada para o banco de dados é geralmente mais eficiente do que consultas
separadas para cada entidade recuperada. Por exemplo, suponha que cada
departamento tenha dez cursos relacionados. O carregamento adiantado de todos os
dados relacionados resultará em apenas uma única consulta (junção) e uma única
viagem de ida e volta para o banco de dados. Uma consulta separada para cursos de
cada departamento resultará em onze viagens de ida e volta para o banco de dados. As
viagens de ida e volta extras para o banco de dados são especialmente prejudiciais ao
desempenho quando a latência é alta.

Por outro lado, em alguns cenários, consultas separadas são mais eficientes. O
carregamento adiantado de todos os dados relacionados em uma consulta pode fazer
com que uma junção muito complexa seja gerada, que o SQL Server não consegue
processar com eficiência. Ou se precisar acessar as propriedades de navegação de uma
entidade somente para um subconjunto de um conjunto de entidades que está sendo
processado, consultas separadas poderão ter um melhor desempenho, pois o
carregamento adiantado de tudo desde o início recupera mais dados do que você
precisa. Se o desempenho for crítico, será melhor testar o desempenho das duas
maneiras para fazer a melhor escolha.
Criar uma página Cursos
A Course entidade inclui uma propriedade de navegação que contém a Department
entidade do departamento à qual o curso é atribuído. Para exibir o nome do
departamento atribuído em uma lista de cursos, você precisa obter a Name propriedade
da Department entidade que está na Course.Department propriedade de navegação.

Crie um controlador nomeado CoursesController para o Course tipo de entidade,


usando as mesmas opções para o Controlador MVC com exibições, usando o
scaffolder Entity Framework que você fez anteriormente para o StudentsController ,
conforme mostrado na ilustração a seguir:

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


especificou o carregamento adiantado para a propriedade de navegação Department
usando o método Include .

Substitua o método Index pelo seguinte código, que usa um nome mais apropriado
para o IQueryable que retorna as entidades Course ( courses em vez de schoolContext ):

C#

public async Task<IActionResult> Index()


{
var courses = _context.Courses
.Include(c => c.Department)
.AsNoTracking();
return View(await courses.ToListAsync());
}
Abra Views/Courses/Index.cshtml e substitua o código do modelo pelo código a seguir.
As alterações são realçadas:

CSHTML

@model IEnumerable<ContosoUniversity.Models.Course>

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

<h2>Courses</h2>

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

Você fez as seguintes alterações no código gerado por scaffolding:

Alterou o título de Índice para Cursos.

Adicionou uma coluna Número que mostra o valor da propriedade CourseID . Por
padrão, as chaves primárias não são geradas por scaffolding porque normalmente
não têm sentido para os usuários finais. No entanto, nesse caso, a chave primária é
significativa e você deseja mostrá-la.

Alterou a coluna Departamento para que ela exiba o nome de departamento. O


código exibe a propriedade Name da entidade Department que é carregada na
propriedade de navegação Department :

HTML

@Html.DisplayFor(modelItem => item.Department.Name)

Execute o aplicativo e selecione a guia Cursos para ver a lista com nomes de
departamentos.
Criar uma página Instrutores
Nesta seção, você criará um controlador e uma exibição para a Instructor entidade
para exibir a página Instrutores:
Essa página lê e exibe dados relacionados das seguintes maneiras:

A lista de instrutores exibe dados relacionados da OfficeAssignment entidade. As


entidades Instructor e OfficeAssignment estão em uma relação um para zero ou
um. Você usará o carregamento ansioso para as OfficeAssignment entidades.
Conforme explicado anteriormente, o carregamento adiantado é geralmente mais
eficiente quando você precisa dos dados relacionados para todas as linhas
recuperadas da tabela primária. Nesse caso, você deseja exibir atribuições de
escritório para todos os instrutores exibidos.

Quando o usuário seleciona um instrutor, as entidades Course relacionadas são


exibidas. As entidades Instructor e Course estão em uma relação muitos para
muitos. Você usará o carregamento ansioso para as Course entidades e suas
entidades relacionadas Department . Nesse caso, consultas separadas podem ser
mais eficientes porque você precisa de cursos somente para o instrutor
selecionado. No entanto, este exemplo mostra como usar o carregamento
adiantado para propriedades de navegação em entidades que estão nas
propriedades de navegação.

Quando o usuário seleciona um curso, os dados relacionados do Enrollments


conjunto de entidades são exibidos. As entidades Course e Enrollment estão em
uma relação um-para-muitos. Você usará consultas separadas para Enrollment
entidades e suas entidades relacionadas Student .

Criar um modelo de exibição para a exibição Índice de


Instrutor
A página Instrutores mostra dados de três tabelas diferentes. Portanto, você criará um
modelo de exibição que inclui três propriedades, cada uma contendo os dados de uma
das tabelas.

Na pasta SchoolViewModels , crie InstructorIndexData.cs e substitua o código existente


pelo seguinte código:

C#

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

namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Criar exibições e o controlador Instrutor
Crie um controlador Instrutores com ações de leitura/gravação do EF, conforme
mostrado na seguinte ilustração:

Abra InstructorsController.cs e adicione uma instrução using para o namespace


ViewModels:

C#

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.

C#

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.

C#

viewModel.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Como o modo de exibição sempre requer a OfficeAssignment entidade, é 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 .

Você pode ler mais sobre como incluir vários níveis de dados relacionados aqui.

C#

viewModel.Instructors = await _context.Instructors


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

Nesse ponto do código, outro ThenInclude se refere às propriedades de navegação de


Student , que não é necessário. Mas a chamada a Include é reiniciada com
propriedades Instructor e, portanto, você precisa passar pela cadeia novamente, dessa
vez, especificando Course.Department em vez de Course.Enrollments .

C#

viewModel.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
O código a seguir é executado quando o instrutor é selecionado. O instrutor
selecionado é recuperado da lista de instrutores no modelo de exibição. A propriedade
do modelo de Courses exibição é carregada com as Course entidades da propriedade
de navegação desse CourseAssignments instrutor.

C#

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 Single
método converte a coleção em uma única Instructor entidade, o que lhe dá acesso à
propriedade dessa CourseAssignments 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 Single método gera uma exceção se a coleção passada para ela estiver vazia ou
se houver 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:

C#

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

Em vez de:

C#

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

Em seguida, se um curso foi selecionado, o curso selecionado é recuperado na lista de


cursos no modelo de exibição. Em seguida, a propriedade Enrollments do modelo de
exibição é carregada com as entidades Enrollment da propriedade de navegação
Enrollments desse curso.

C#

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

Modificar a exibição Índice de Instrutor


In Views/Instructors/Index.cshtml , substitua o código do modelo pelo código a seguir.
As alterações são realçadas.

CSHTML

@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

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

<h2>Instructors</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructors)
{
string selectedRow = "";
if (item.ID == (int?)ViewData["InstructorID"])
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @course.Course.Title <br />
}
</td>
<td>
<a asp-action="Index" asp-route-id="@item.ID">Select</a>
|
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

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

Alterou a classe de modelo para InstructorIndexData .

Alterou o título de página de Índice para Instrutores.

Adicionou uma coluna Office que exibe item.OfficeAssignment.Location somente


se item.OfficeAssignment não é nulo. (Como essa é uma relação um para zero ou
um, pode não haver uma entidade OfficeAssignment relacionada.)

CSHTML

@if (item.OfficeAssignment != null)


{
@item.OfficeAssignment.Location
}
Adicionou uma coluna Courses que exibe os cursos ministrados por cada instrutor.
Para obter mais informações, consulte a seção transição de linha explícita do Razor
artigo de sintaxe.

Código adicionado que adiciona condicionalmente uma classe CSS bootstrap ao


tr elemento do instrutor selecionado. Essa classe define uma cor de plano de

fundo para a linha selecionada.

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 .

CSHTML

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

Execute o aplicativo e selecione a guia Instrutores . A página exibe a propriedade


Location de entidades relacionadas do OfficeAssignment e uma célula de tabela vazia
quando não há nenhuma entidade relacionada do OfficeAssignment.

Views/Instructors/Index.cshtml No arquivo, após o elemento de tabela de fechamento

(no final do arquivo), adicione o código a seguir. Esse código exibe uma lista de cursos
relacionados a um instrutor quando um instrutor é selecionado.

CSHTML
@if (Model.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>

@foreach (var item in Model.Courses)


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

</table>
}

Esse código lê a propriedade Courses do modelo de exibição para exibir uma lista de
cursos. Também fornece um hiperlink Selecionar que envia a ID do curso selecionado
para o método de ação Index .

Atualize a página e selecione um instrutor. Agora, você verá uma grade que exibe os
cursos atribuídos ao instrutor selecionado, e para cada curso, verá o nome do
departamento atribuído.
Após o bloco de código que você acabou de adicionar, adicione o código a seguir. Isso
exibe uma lista dos alunos que estão registrados em um curso quando esse curso é
selecionado.

CSHTML

@if (Model.Enrollments != null)


{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}

Esse código lê a Enrollments propriedade do modelo de exibição para exibir uma lista
de 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.
Sobre o carregamento explícito
Ao recuperar a lista de instrutores InstructorsController.cs , você especificou o
carregamento ansioso para a CourseAssignments propriedade de navegação.

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 o carregamento
explícito, substitua o Index método pelo código a seguir, que remove o carregamento
ansioso e Enrollments carrega essa propriedade explicitamente. As alterações de
código são realçadas.

C#

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 ThenInclude chamadas de método para dados de registro do


código que recupera entidades de instrutor. Também solta AsNoTracking . Se um
instrutor e um curso forem selecionados, o código realçado recuperará entidades
Enrollment para o curso selecionado e Student entidades 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.

Obter o código
Baixe ou exiba o aplicativo concluído.

Próximas etapas
Neste tutorial, você:

" Aprendeu a carregar dados relacionados


" Criou uma página Cursos
" Criou uma página Instrutores
" Aprendeu sobre o carregamento explícito

Vá para o próximo tutorial para aprender a atualizar dados relacionados.

Atualizar dados relacionados


Tutorial: Atualizar dados relacionados –
ASP.NET MVC com EF Core
Artigo • 28/11/2022 • 19 minutos para o fim da leitura

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á.
Neste tutorial, você:

" Personalizar as páginas Cursos


" Adicionar a página Editar Instrutores
" Adicionar cursos à página Editar
" Atualizar a página Excluir
" Adicionar o local do escritório e cursos à página Criar

Pré-requisitos
Ler dados relacionados
Personalizar as páginas Cursos
Quando uma nova Course entidade é criada, ela deve 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 Course.DepartmentID propriedade
de chave estrangeira e isso é tudo o que o Entity Framework precisa para carregar a
Department propriedade de navegação com a entidade apropriada Department . 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 Criar e Editar e substitua-os pelo


seguinte código:

C#

public IActionResult Create()


{
PopulateDepartmentsDropDownList();
return View();
}

C#

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

C#

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


{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}

C#

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

var courseToUpdate = await _context.Courses


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

if (await TryUpdateModelAsync<Course>(courseToUpdate,
"",
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}

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

C#
private void PopulateDepartmentsDropDownList(object selectedDepartment =
null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery.AsNoTracking(),
"DepartmentID", "Name", selectedDepartment);
}

O método PopulateDepartmentsDropDownList obtém uma lista de todos os


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

O método HttpGet Create chama o método PopulateDepartmentsDropDownList sem


definir o item selecionado, porque um novo curso do departamento ainda não foi
estabelecido:

C#

public IActionResult Create()


{
PopulateDepartmentsDropDownList();
return View();
}

O método HttpGet Edit define o item selecionado, com base na ID do departamento já


atribuído ao curso que está sendo editado:

C#

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


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

var course = await _context.Courses


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

Os métodos HttpPost para Create e Edit também incluem o código que define o item
selecionado quando eles exibem novamente a página após um erro. Isso garante que
quando a página for exibida novamente para mostrar a mensagem de erro, qualquer
que tenha sido o departamento selecionado permaneça selecionado.

Adicionar .AsNoTracking aos métodos Details e Delete


Para otimizar o desempenho das páginas Detalhes do Curso e Excluir, adicione
chamadas AsNoTracking aos métodos HttpGet Details e Delete .

C#

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


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

var course = await _context.Courses


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

return View(course);
}

C#

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


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

var course = await _context.Courses


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

return View(course);
}

Modificar as exibições Curso


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

CSHTML

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

In Views/Courses/Edit.cshtml , make the same change for the Department field that you
just did in Create.cshtml .

Views/Courses/Edit.cshtml Além disso, adicione um campo 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.

CSHTML

<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.
In Views/Courses/Delete.cshtml , adicione um campo número de curso na parte superior
e altere a ID do departamento para o nome do departamento.

CSHTML

@model ContosoUniversity.Models.Course

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

<h2>Delete</h2>

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


<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.CourseID)
</dd>
<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.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Name)
</dd>
</dl>

<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
In Views/Courses/Details.cshtml , make the same change that you just did for
Delete.cshtml .

Testar as páginas Curso


Execute o aplicativo, selecione a guia Cursos, clique em Criar Novo e insira dados para
um novo curso:

Clique em Criar. A página Índice de Cursos é exibida com o novo curso adicionado à
lista. O nome do departamento na lista de páginas de Índice é obtido da propriedade
de navegação, mostrando que a relação foi estabelecida corretamente.

Clique em Editar em um curso na página Índice de Cursos.


Altere dados na página e clique em Salvar. A página Índice de Cursos é exibida com os
dados de cursos atualizados.

Adicionar a página Editar Instrutores


Quando você edita um registro de instrutor, deseja poder atualizar a atribuição de
escritório do instrutor. A Instructor entidade tem uma relação de um para zero ou um
com a entidade, o OfficeAssignment que significa que seu código precisa lidar com as
seguintes situações:

Se o usuário limpar a atribuição do office e tiver originalmente um valor, exclua a


OfficeAssignment entidade.

Se o usuário inserir um valor de atribuição do office e ele estiver originalmente


vazio, crie uma nova OfficeAssignment entidade.
Se o usuário alterar o valor de uma atribuição de escritório, altere o valor em uma
entidade existente OfficeAssignment .

Atualizar o controlador Instrutores


In InstructorsController.cs , altere o código no método HttpGet Edit para que ele
carregue a propriedade de navegação da OfficeAssignment entidade Instrutor e chame
AsNoTracking :

C#

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


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

var instructor = await _context.Instructors


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

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

C#

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

var instructorToUpdate = await _context.Instructors


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

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i =>
i.OfficeAssignment))
{
if
(String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
return View(instructorToUpdate);
}

O código faz o seguinte:

Altera o nome do método para EditPost porque a assinatura agora é a mesma do


método HttpGet Edit (o atributo ActionName especifica que a URL /Edit/ ainda é
usada).

Obtém a entidade Instructor atual do banco de dados usando o carregamento


adiantado para a propriedade de navegação OfficeAssignment . Isso é o mesmo
que você fez no método HttpGet Edit .

Atualiza a entidade Instructor recuperada com valores do associador de modelos.


A TryUpdateModel sobrecarga permite declarar as propriedades que você deseja
incluir. Isso impede o excesso de postagem, conforme explicado no segundo
tutorial.

C#

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i =>
i.OfficeAssignment))
Se o local do escritório estiver em branco, definirá a Instructor.OfficeAssignment
propriedade como nula para que a linha relacionada na OfficeAssignment tabela
seja excluída.

C#

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

Salva as alterações no banco de dados.

Atualizar a exibição Editar Instrutor


In Views/Instructors/Edit.cshtml , add a new field for editing the office location, at the
end before the Save button:

CSHTML

<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 cursos à página Editar
Os instrutores podem ministrar a quantidade de cursos que desejarem. Agora você
aprimora 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 entidades Course e Instructor entidades é muitos para muitos. Para
adicionar e remover relações, adicione e remova entidades de e para o
CourseAssignments conjunto de entidades de junção.

A interface do usuário que permite alterar os cursos aos quais um instrutor é atribuído é
um grupo de caixas de seleção. Uma caixa de seleção para cada curso no banco de
dados é exibida e as que o instrutor está atribuído atualmente são selecionadas. 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 da 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:

C#

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

In InstructorsController.cs , substitua o método HttpGet Edit pelo código a seguir. As


alterações são realçadas.

C#

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


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

var instructor = await _context.Instructors


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

private void PopulateAssignedCourseData(Instructor instructor)


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

O código adiciona carregamento ansioso para a Courses propriedade de navegação e


chama o novo PopulateAssignedCourseData método para fornecer informações para a
matriz da caixa de seleção usando a classe de modelo de exibição AssignedCourseData .

O código no PopulateAssignedCourseData método lê todas as Course entidades 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 Assigned
propriedade é definida como true para cursos aos quais o instrutor é atribuído. O modo
de exibição usará essa propriedade para determinar quais caixas de seleção devem 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.

C#

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

var instructorToUpdate = await _context.Instructors


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

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i =>
i.OfficeAssignment))
{
if
(String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}

C#
private void UpdateInstructorCourses(string[] selectedCourses, Instructor
instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c =>
c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new
CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID =
course.CourseID });
}
}
else
{

if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(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 CourseAssignments propriedade da associação de modelo. Isso não


requer nenhuma alteração no código que chama TryUpdateModel porque você está
usando a sobrecarga que requer aprovação explícita e CourseAssignments não está na
lista de inclusão.
Se nenhuma caixa de seleção tiver sido selecionada, o código em
UpdateInstructorCourses inicializará a CourseAssignments propriedade de navegação
com uma coleção vazia e retornará:

C#

private void UpdateInstructorCourses(string[] selectedCourses, Instructor


instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c =>
c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new
CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID =
course.CourseID });
}
}
else
{

if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(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 de um curso tiver sido selecionada, mas o curso não estiver na
Instructor.CourseAssignments propriedade de navegação, o curso será adicionado à
coleção na propriedade de navegação.

C#

private void UpdateInstructorCourses(string[] selectedCourses, Instructor


instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c =>
c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new
CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID =
course.CourseID });
}
}
else
{

if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID ==
course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

Se a caixa de seleção de um curso não tiver sido selecionada, mas o curso estiver na
Instructor.CourseAssignments propriedade de navegação, o curso será removido da
propriedade de navegação.

C#
private void UpdateInstructorCourses(string[] selectedCourses, Instructor
instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c =>
c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new
CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID =
course.CourseID });
}
}
else
{

if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID ==
course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

Atualizar as exibições Instrutor


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

7 Observação

Quando você colar o código no Visual Studio, as quebras de linha poderão ser
alteradas de uma forma que divide o código. Se o código ficar com aparência
diferente depois de colá-lo, 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 runtime. Com o bloco de novo código
selecionado, pressione Tab três vezes para alinhar o novo código com o código
existente. Esse problema foi corrigido no Visual Studio 2019.

CSHTML

<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;

List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses =
ViewBag.Courses;

foreach (var course in courses)


{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ?
"checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>

Esse código cria uma tabela HTML que contém três colunas. Em cada coluna há uma
caixa de seleção seguida de uma legenda que consiste no número e no título do curso.
Todas as caixas de seleção têm o mesmo nome ("selectedCourses"), que informa ao
associador de modelo que elas devem ser tratadas como um grupo. O atributo de valor
de cada caixa de seleção é definido como o valor de CourseID . Quando a página é
postada, o associador de modelo passa uma matriz para o controlador que consiste nos
CourseID valores somente para as caixas de seleção selecionadas.

Quando as caixas de seleção são renderizadas inicialmente, aquelas que são para cursos
atribuídos ao instrutor têm atributos verificados, o que as seleciona (exibe-os
verificados).

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.
7 Observação

A abordagem usada aqui para editar os dados de curso do instrutor funciona bem
quando há uma quantidade limitada de cursos. Para coleções muito maiores, uma
interface do usuário e um método de atualização diferentes são necessários.

Atualizar a página Excluir


Em InstructorsController.cs , exclua o DeleteConfirmed método e insira o código a
seguir em seu lugar.

C#

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Instructor instructor = await _context.Instructors
.Include(i => i.CourseAssignments)
.SingleAsync(i => i.ID == id);

var departments = await _context.Departments


.Where(d => d.InstructorID == id)
.ToListAsync();
departments.ForEach(d => d.InstructorID = null);

_context.Instructors.Remove(instructor);

await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

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

Executa o carregamento adiantado para a propriedade de navegação


CourseAssignments . Você precisa incluir isso ou o EF não reconhecerá as entidades

CourseAssignment relacionadas e não as excluirá. Para evitar a necessidade de lê-


las aqui, você pode configurar a exclusão em cascata no banco de dados.

Se o instrutor a ser excluído é atribuído como administrador de qualquer


departamento, remove a atribuição de instrutor desse departamento.
Adicionar o local do escritório e cursos à
página Criar
In InstructorsController.cs , delete the HttpGet and HttpPost Create methods, and
then add the following code in their place:

C#

public IActionResult Create()


{
var instructor = new Instructor();
instructor.CourseAssignments = new List<CourseAssignment>();
PopulateAssignedCourseData(instructor);
return View();
}

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

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

Observe que para poder adicionar cursos à propriedade de navegação


CourseAssignments , é necessário inicializar a propriedade como uma coleção vazia:

C#

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

Como alternativa para fazer isso no código do controlador, você pode fazê-lo no
Instructor modelo alterando o getter de propriedade para criar automaticamente a

coleção se ela não existir, conforme mostrado no exemplo a seguir:

C#

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.

In Views/Instructor/Create.cshtml , adicione uma caixa de texto de local de 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.

CSHTML

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

<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;

List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses =
ViewBag.Courses;

foreach (var course in courses)


{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ?
"checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>

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

Manipulando transações
Conforme explicado no tutorial do CRUD, o Entity Framework implementa transações de
forma implícita. Para cenários em que você precisa de mais controle – por exemplo, se
desejar incluir operações feitas fora do Entity Framework em uma transação, consulte
Transações.

Obter o código
Baixe ou exiba o aplicativo concluído.
Próximas etapas
Neste tutorial, você:

" Personalizou as páginas Cursos


" Adicionou a página Editar Instrutores
" Adicionou cursos à página Editar
" Atualizou a página Excluir
" Adicionou o local do escritório e cursos à página Criar

Vá para o próximo tutorial para saber como lidar com conflitos de simultaneidade.

Tratar conflitos de simultaneidade


Tutorial: Manipular simultaneidade –
ASP.NET MVC com EF Core
Artigo • 28/11/2022 • 19 minutos para o fim da leitura

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 Department entidade e lidam com
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.
Neste tutorial, você:

" Aprender sobre conflitos de simultaneidade


" Adicionar uma propriedade de acompanhamento
" Criar um controlador e exibições de Departamentos
" Atualizar a exibição do Índice
" Atualizar métodos de Edição
" Atualizar a exibição de Edição
" Testar os conflitos de simultaneidade
" Atualizar a página Excluir
" Exibições Atualizar Detalhes e Criar

Pré-requisitos
Atualizar dados relacionados

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. Manter grandes quantidades
de estado pode afetar o desempenho do aplicativo porque ele requer recursos do
servidor ou deve ser incluído 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 que
está no armazenamento de dados.) Conforme observado na introdução a esta
seção, se você não fizer qualquer codificação para tratamento 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 Store Wins neste tutorial. Esse método garante que
nenhuma alteração é substituída sem que um usuário seja alertado sobre o que
está acontecendo.
Detectando conflitos de simultaneidade
Resolva conflitos manipulando exceções DbConcurrencyException geradas pelo Entity
Framework. Para saber quando gerar essas exceções, o Entity Framework precisa poder
detectar conflitos. Portanto, é necessário configurar o banco de dados e o modelo de
dados de forma adequada. Algumas opções para habilitar a detecção de conflitos
incluem as seguintes:

Na tabela de banco de dados, inclua uma coluna de acompanhamento que pode


ser usada para determinar quando uma linha é alterada. Em seguida, configure o
Entity Framework para incluir essa coluna na cláusula Where de comandos SQL
Update ou Delete.

O tipo de dados da coluna de acompanhamento é normalmente rowversion . O


valor rowversion é um número sequencial que é incrementado sempre que a linha
é atualizada. Em um comando Update ou Delete, a cláusula Where inclui o valor
original da coluna de acompanhamento (a versão de linha original). Se a linha que
está sendo atualizada tiver sido alterada por outro usuário, o valor da coluna
rowversion será diferente do valor original; portanto, a instrução Update ou Delete

não poderá encontrar a linha a ser atualizada devido à cláusula Where. Quando o
Entity Framework descobre que nenhuma linha foi atualizada pelo comando
Update ou Delete (ou seja, quando o número de linhas afetadas é zero), ele
interpreta isso como um conflito de simultaneidade.

Configure o Entity Framework para incluir os valores originais de cada coluna na


tabela na cláusula Where de comandos Update e Delete.

Como a primeira opção, se nada na linha for alterado desde que a linha tiver sido
lida pela primeira vez, a cláusula Where não retornará uma linha a ser atualizada, o
que o Entity Framework interpretará como um conflito de simultaneidade. Para
tabelas de banco de dados que têm muitas colunas, essa abordagem pode resultar
em cláusulas Where muito grandes e pode exigir que você mantenha grandes
quantidades de estado. Conforme observado anteriormente, manter grandes
quantidades de estado pode afetar o desempenho do aplicativo. Portanto, essa
abordagem geralmente não é recomendada e não é o método usado neste
tutorial.

Caso deseje implementar essa abordagem, precisará marcar todas as propriedades


de chave não primária na entidade para as quais você deseja controlar a
simultaneidade adicionando o atributo ConcurrencyCheck a elas. Essa alteração
permite que o Entity Framework inclua todas as colunas na cláusula Where do SQL
de instruções Update e Delete.
No restante deste tutorial, você adicionará uma propriedade de acompanhamento
rowversion à entidade Department, criará um controlador e exibições e testará para
verificar se tudo está funcionando corretamente.

Adicionar uma propriedade de


acompanhamento
In Models/Department.cs , adicione uma propriedade de acompanhamento chamada
RowVersion:

C#

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

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

[Timestamp]
public byte[] RowVersion { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

O atributo Timestamp especifica que essa coluna será incluída na cláusula Where de
comandos Update e Delete enviados ao banco de dados. O atributo é chamado
Timestamp porque as versões anteriores do SQL Server usavam um tipo de dados

timestamp do SQL antes de o rowversion do SQL substituí-lo. O tipo do .NET para


rowversion é uma matriz de bytes.

Se você preferir usar a API fluente, poderá usar o IsConcurrencyToken método (em
Data/SchoolContext.cs ) para especificar a propriedade de acompanhamento, conforme

mostrado no exemplo a seguir:

C#

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:

CLI do .NET

dotnet ef migrations add RowVersion

CLI do .NET

dotnet ef database update

Criar um controlador e exibições de


Departamentos
Gere por scaffolding um controlador e exibições Departamentos, como você fez
anteriormente para Alunos, Cursos e Instrutores.
DepartmentsController.cs No arquivo, altere todas as quatro ocorrências de
"FirstMidName" para "FullName" para que as listas suspensas do administrador do
departamento contenham o nome completo do instrutor em vez de apenas o
sobrenome.

C#

ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID",


"FullName", department.InstructorID);

Atualizar a exibição do Índice


O mecanismo de scaffolding criou uma RowVersion coluna na exibição Índice, mas esse
campo não deve ser exibido.

Substitua o código Views/Departments/Index.cshtml pelo código a seguir.

CSHTML

@model IEnumerable<ContosoUniversity.Models.Department>

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

<h2>Departments</h2>

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

Isso altera o título para "Departamentos", exclui a coluna e mostra o RowVersion nome
completo em vez do nome do administrador.
Atualizar métodos de Edição
Nos métodos HttpGet Edit e Details , adicione AsNoTracking . No método HttpGet
Edit , adicione o carregamento adiantado ao Administrador.

C#

var department = await _context.Departments


.Include(i => i.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);

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

C#

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

var departmentToUpdate = await _context.Departments.Include(i =>


i.Administrator).FirstOrDefaultAsync(m => m.DepartmentID == id);

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

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

if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by
another user.");
}
else
{
var databaseValues = (Department)databaseEntry.ToObject();

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

ModelState.AddModelError(string.Empty, "The record you


attempted to edit "
+ "was modified by another user after you got the
original value. The "
+ "edit operation was canceled and the current
values in the database "
+ "have been displayed. If you still want to edit
this record, click "
+ "the Save button again. Otherwise click the Back
to List hyperlink.");
departmentToUpdate.RowVersion =
(byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");
}
}
}
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID",
"FullName", departmentToUpdate.InstructorID);
return View(departmentToUpdate);
}

O código começa com a tentativa de ler o departamento a ser atualizado. Se o método


FirstOrDefaultAsync 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 Department entidade para que a página Editar possa ser reprodutorizada com uma
mensagem de erro. Como alternativa, você não precisará recriar a Department entidade
se exibir apenas uma mensagem de erro sem refazer os campos do 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.

C#

_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 original RowVersion ), o Entity Framework gerará uma
DbUpdateConcurrencyException exceção.

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.

C#

var exceptionEntry = ex.Entries.Single();

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

C#
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();

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

C#

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.

C#

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

A instrução ModelState.Remove é obrigatória porque ModelState tem o valor RowVersion


antigo. Na exibição, o valor ModelState de um campo tem precedência sobre os valores
de propriedade do modelo, quando ambos estão presentes.

Atualizar a exibição de Edição


Em Views/Departments/Edit.cshtml , faça as seguintes alterações:

Adicione um campo oculto para salvar o valor da propriedade RowVersion ,


imediatamente após o campo oculto da propriedade DepartmentID .

Adicione uma opção "Selecionar Administrador" à lista suspensa.

CSHTML
@model ContosoUniversity.Models.Department

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

<h2>Edit</h2>

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

<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Testar os conflitos de simultaneidade


Execute o aplicativo e acesse a página Índice de Departamentos. Clique com o botão
direito do mouse na hiperlink Editar do departamento de inglês e selecione Abrir em
uma nova guia e, em seguida, clique no hiperlink Editar no departamento de inglês. As
duas guias do navegador agora exibem as mesmas informações.

Altere um campo na primeira guia do navegador e clique em Salvar.

O navegador mostra a página Índice com o valor alterado.

Altere um campo na segunda guia do navegador.


Clique em Salvar. Você verá uma mensagem de erro:
Clique em Salvar novamente. O valor inserido na segunda guia do navegador foi salvo.
Você verá os valores salvos quando a página Índice for exibida.

Atualizar a página Excluir


Para a página Excluir, o Entity Framework detecta conflitos de simultaneidade causados
pela edição por outra pessoa do departamento de maneira semelhante. Quando o
método HttpGet Delete exibe a exibição de confirmação, a exibição inclui o valor
RowVersion original em um campo oculto. Em seguida, esse valor estará disponível para

o método HttpPost Delete chamado quando o usuário confirmar a exclusão. Quando o


Entity Framework cria o comando SQL DELETE, ele inclui uma cláusula WHERE com o
valor RowVersion original. Se o comando não resultar em nenhuma linha afetada (o que
significa que a linha foi alterada após a exibição da página Confirmação de exclusão),
uma exceção de simultaneidade será gerada e o método HttpGet Delete será chamado
com um sinalizador de erro definido como verdadeiro para exibir novamente a página
de confirmação com uma mensagem de erro. Também é possível que nenhuma linha
tenha sido afetada porque a linha foi excluída por outro usuário; portanto, nesse caso,
nenhuma mensagem de erro é exibida.

Atualizar os métodos Excluir no controlador


Departamentos
Em DepartmentsController.cs , substitua o método HttpGet Delete pelo seguinte
código:

C#

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


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

var department = await _context.Departments


.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);
if (department == null)
{
if (concurrencyError.GetValueOrDefault())
{
return RedirectToAction(nameof(Index));
}
return NotFound();
}
if (concurrencyError.GetValueOrDefault())
{
ViewData["ConcurrencyErrorMessage"] = "The record you attempted to
delete "
+ "was modified by another user after you got the original
values. "
+ "The delete operation was canceled and the current values in
the "
+ "database have been displayed. If you still want to delete
this "
+ "record, click the Delete button again. Otherwise "
+ "click the Back to List hyperlink.";
}

return View(department);
}

O método aceita um parâmetro opcional que indica se a página está sendo exibida
novamente após um erro de simultaneidade. Se esse sinalizador é verdadeiro e o
departamento especificado não existe mais, isso indica que ele foi excluído por outro
usuário. Nesse caso, o código redireciona para a página Índice. Se esse sinalizador for
verdadeiro e o departamento existir, ele será 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:

C#

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

C#

public async Task<IActionResult> DeleteConfirmed(int id)

Você alterou esse parâmetro para uma Department instância de entidade criada pelo
associador de modelo. Isso fornece acesso EF ao valor da propriedade RowVers'ion,
além da chave de registro.

C#

public async Task<IActionResult> Delete(Department department)

Você também alterou o nome do método de ação de DeleteConfirmed para Delete . O


código gerado por scaffolding usou o nome DeleteConfirmed para fornecer ao método
HttpPost uma assinatura exclusiva. (O CLR requer métodos sobrecarregados para ter
parâmetros de método diferentes.) Agora que as assinaturas são exclusivas, você pode
manter a convenção do 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 scaffolded pelo código a
seguir que adiciona um campo de mensagem de erro e campos ocultos para as
propriedades DepartmentID e RowVersion. As alterações são realçadas.

CSHTML

@model ContosoUniversity.Models.Department

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

<h2>Delete</h2>
<p class="text-danger">@ViewData["ConcurrencyErrorMessage"]</p>

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


<div>
<h4>Department</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Budget)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
</dl>

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

Isso faz as seguintes alterações:

Adiciona uma mensagem de erro entre os cabeçalhos h2 e h3 .

Substitua FirstMidName por FullName no campo Administrador.

Remove o campo RowVersion.

Adiciona um campo oculto à propriedade RowVersion .


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

Na primeira janela, altere um dos valores e clique em Salvar:

Na segunda guia, clique em Excluir. Você verá a mensagem de erro de simultaneidade e


os valores de Departamento serão atualizados com o que está atualmente no banco de
dados.
Se você clicar em Excluir novamente, será redirecionado para a página Índice, que
mostra que o departamento foi excluído.

Exibições Atualizar Detalhes e Criar


Opcionalmente, você pode limpar o código gerado por scaffolding nas exibições
Detalhes e Criar.

Substitua o código Views/Departments/Details.cshtml para excluir a coluna RowVersion


e mostrar o nome completo do Administrador.

CSHTML

@model ContosoUniversity.Models.Department
@{
ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
<h4>Department</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Budget)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd class="col-sm-10">
@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 Views/Departments/Create.cshtml para adicionar uma opção


Selecionar à lista suspensa.

CSHTML

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

Obter o código
Baixe ou exiba o aplicativo concluído.
Recursos adicionais
Para obter mais informações sobre como lidar com a simultaneidade, EF Coreconsulte
conflitos de simultaneidade.

Próximas etapas
Neste tutorial, você:

" Aprendeu sobre conflitos de simultaneidade


" Adicionou uma propriedade de acompanhamento
" Criou um controlador e exibições de Departamentos
" Atualizou a exibição do Índice
" Atualizou métodos de Edição
" Atualizou a exibição de Edição
" Testou conflitos de simultaneidade
" Atualizou a página Excluir
" Atualizou as exibições de Detalhes e Criar

Vá para o próximo tutorial para aprender a implementar a herança de tabela por


hierarquia para as entidades Instructor e Student.

Próximo: Implementar herança de tabela por hierarquia


Tutorial: Implementar herança –
ASP.NET MVC com EF Core
Artigo • 28/11/2022 • 8 minutos para o fim da leitura

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.

Neste tutorial, você:

" Mapeará a herança para o banco de dados


" Criar a classe Person
" Atualizará Instructor e Student
" Adicionará Person ao modelo
" Criará e atualizará migrações
" Testará a implementação

Pré-requisitos
Manipular a simultaneidade

Mapeará a herança para o 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 Person tabela que inclua 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 de uma única tabela
de banco de dados é 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ê poderia ter apenas os campos de nome na Person tabela e
ter tabelas e Student tabelas separadas Instructor com os campos de data.

2 Aviso

O TPT (Table-Per-Type) não tem suporte no EF Core 3.x, no entanto, ele foi
implementado em EF Core 5.0.

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 (Classe
Table-per-Concrete ). Se você implementasse a herança TPC para o Person , Student e
Instructor classes, como mostrado anteriormente, as tabelas e Instructor as Student
tabelas não seriam diferentes depois de implementar a herança do que antes.

Em geral, os padrões de herança TPC e TPH oferecem melhor desempenho do que os


padrões de herança TPT, porque os padrões TPT podem resultar em consultas de junção
complexas.

Este tutorial demonstra como implementar a herança TPH. TPH é o único padrão de
herança compatível com o Entity Framework Core. O que você fará é criar uma classe
Person , alterar as classes Instructor e Student para que elas derivem de Person ,
adicionar a nova classe ao DbContext e criar uma migração.

 Dica

Considere a possibilidade de salvar uma cópia do projeto antes de fazer as


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

Criar a classe Person


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

C#

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

namespace ContosoUniversity.Models
{
public abstract class Person
{
public int ID { get; set; }

[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than
50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
}
}

Atualizará Instructor e Student


Em Instructor.cs , derive a classe Instrutor da classe Person e remova os campos chave
e nome. O código será semelhante ao seguinte exemplo:

C#

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

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

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


public OfficeAssignment OfficeAssignment { get; set; }
}
}

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

C#

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

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

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


}
}

Adicionará Person ao modelo


Adicione o tipo de entidade Person a SchoolContext.cs . As novas linhas são realçadas.

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
public DbSet<Person> People { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>
().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>
().ToTable("CourseAssignment");
modelBuilder.Entity<Person>().ToTable("Person");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}

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

Criará e atualizará migrações


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

CLI do .NET

dotnet ef migrations add Inheritance

Não execute o comando database update ainda. Esse comando resultará em perda de
dados porque ele removerá a tabela Instrutor e renomeará a tabela Aluno como Pessoa.
Você precisa fornecer o código personalizado para preservar os dados existentes.

Abra Migrations/<timestamp>_Inheritance.cs e substitua o Up método pelo seguinte


código:

C#

protected override void Up(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropForeignKey(
name: "FK_Enrollment_Student_StudentID",
table: "Enrollment");

migrationBuilder.DropIndex(name: "IX_Enrollment_StudentID", table:


"Enrollment");

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


migrationBuilder.AddColumn<DateTime>(name: "EnrollmentDate", table:
"Person", nullable: true);
migrationBuilder.AddColumn<string>(name: "Discriminator", table:
"Person", nullable: false, maxLength: 128, defaultValue: "Instructor");
migrationBuilder.AlterColumn<DateTime>(name: "HireDate", table:
"Person", nullable: true);
migrationBuilder.AddColumn<int>(name: "OldId", table: "Person",
nullable: true);
// Copy existing Student data into new Person table.
migrationBuilder.Sql("INSERT INTO dbo.Person (LastName, FirstName,
HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName,
null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, ID AS OldId
FROM dbo.Student");
// Fix up existing relationships to match new PK's.
migrationBuilder.Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID
FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator =
'Student')");

// Remove temporary key


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

migrationBuilder.DropTable(
name: "Student");

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

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

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

Remove as restrições de chave estrangeira e índices que apontam para a tabela


Aluno.

Renomeia a tabela Instrutor como Pessoa e faz as alterações necessárias para que
ela armazene dados de Aluno:

Adiciona EnrollmentDate que permite valo nulo para os alunos.

Adiciona a coluna Discriminatória para indicar se uma linha refere-se a um aluno


ou um instrutor.

Faz com que HireDate permita valor nulo, pois linhas de alunos não terão datas de
contratação.

Adiciona um campo temporário que será usado para atualizar chaves estrangeiras
que apontam para alunos. Quando você copiar os alunos para a tabela Person, eles
receberão novos valores de chave primária.
Copia os dados da tabela Aluno para a tabela Pessoa. Isso faz com que os alunos
recebam novos valores de chave primária.

Corrige valores de chave estrangeira que apontam para alunos.

Recria restrições de chave estrangeira e índices, agora apontando-os para a tabela


Person.

(Se você tiver usado o GUID, em vez de inteiro como o tipo de chave primária, os
valores de chave primária dos alunos não precisarão ser alterados e várias dessas etapas
poderão ser omitidas.)

Executar o comando database update :

CLI do .NET

dotnet ef database update

(Em um sistema de produção, você faria alterações correspondentes ao Down método


no caso de precisar usá-lo para voltar à versão anterior do banco de dados. Para este
tutorial, você não usará o Down método.)

7 Observação

É possível receber outros erros ao fazer alterações de esquema em um banco de


dados que contém dados existentes. Se você receber erros de migração que não
consegue resolver, altere o nome do banco de dados na cadeia de conexão ou
exclua o banco de dados. Com um novo banco de dados, não há nenhum dado a
ser migrado e o comando de atualização de banco de dados terá uma
probabilidade maior de ser concluído sem erros. Para excluir o banco de dados, use
o SSOX ou execute o comando database drop da CLI.

Testará a implementação
Execute o aplicativo e teste várias páginas. Tudo funciona da mesma maneira que antes.

No Pesquisador de Objetos do SQL Server, expanda Data Connections/SchoolContext


e, em seguida, Tabelas e você verá que as tabelas Aluno e Instrutor foram substituídas
por uma tabela Pessoa. Abra o designer de tabela Pessoa e você verá que ela contém
todas as colunas que costumavam estar nas tabelas Aluno e Instrutor.
Clique com o botão direito do mouse na tabela Person e, em seguida, clique em
Mostrar Dados da Tabela para ver a coluna discriminatória.

Obter o código
Baixe ou exiba o aplicativo concluído.
Recursos adicionais
Para obter mais informações sobre a herança no Entity Framework Core, consulte
Herança.

Próximas etapas
Neste tutorial, você:

" Mapeou a herança para o banco de dados


" Criou a classe Person
" Atualizou Instructor e Student
" Adicionou Person ao modelo
" Criou e atualizou migrações
" Testou a implementação

Vá para o próximo tutorial para saber como lidar com vários cenários relativamente
avançados do Entity Framework.

Próximo: Tópicos avançados


Tutorial: Saiba mais sobre cenários
avançados – ASP.NET MVC com EF Core
Artigo • 28/11/2022 • 14 minutos para o fim da leitura

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.

Neste tutorial, você:

" Executar consultas SQL brutas


" Chamar uma consulta para retornar entidades
" Chamar uma consulta para outros tipos
" Chamar uma consulta de atualização
" Examinar consultas SQL
" Criar uma camada de abstração
" Aprender sobre a Detecção automática de alterações
" Saiba mais sobre EF Core o código-fonte e os planos de desenvolvimento
" Aprender a usar o LINQ dinâmico para simplificar o código

Pré-requisitos
Implementar Herança

Executar consultas SQL brutas


Uma das vantagens de usar o Entity Framework é que ele evita vincular o código de
forma muito próxima a um método específico de armazenamento de dados. Ele faz isso
pela geração de consultas SQL e comandos para você, que também libera você da
necessidade de escrevê-los. Mas há casos excepcionais em que você precisa executar
consultas SQL específicas criadas manualmente. Para esses cenários, a API do Code First
do Entity Framework inclui métodos que permitem passar comandos SQL diretamente
para o banco de dados. Você tem as seguintes opções na EF Core 1.0:

Use o método DbSet.FromSql para consultas que retornam tipos de entidade. Os


objetos retornados precisam ser do tipo esperado pelo objeto DbSet e são
controlados automaticamente pelo contexto de banco de dados, a menos que
você desative o controle.
Use o Database.ExecuteSqlCommand para comandos que não sejam de consulta.

Caso precise executar uma consulta que retorna tipos que não são entidades, use o
ADO.NET com a conexão de banco de dados fornecida pelo EF. Os dados retornados
não são controlados pelo contexto de banco de dados, mesmo se esse método é usado
para recuperar tipos de entidade.

Como é sempre verdadeiro quando você executa comandos SQL em um aplicativo Web,
é necessário tomar precauções para proteger o site contra ataques de injeção de SQL.
Uma maneira de fazer isso é usar consultas parametrizadas para garantir que as cadeias
de caracteres enviadas por uma página da Web não possam ser interpretadas como
comandos SQL. Neste tutorial, você usará consultas parametrizadas ao integrar a
entrada do usuário a uma consulta.

Chamar uma consulta para retornar entidades


A classe DbSet<TEntity> fornece um método que você pode usar para executar uma
consulta que retorna uma entidade do tipo TEntity . Para ver como isso funciona, você
alterará o código no método Details do controlador Departamento.

No DepartmentsController.cs método, Details substitua o código que recupera um


departamento por uma FromSql chamada de método, conforme mostrado no seguinte
código realçado:

C#

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


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

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


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

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 para 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. Na EF Core 1.0, uma maneira de fazer isso é gravar ADO.NET código e obter a
conexão de banco de dados do EF.

In HomeController.cs , substitua o About método pelo seguinte código:

C#

public async Task<ActionResult> About()


{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount
"
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();

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

Adicionar uma instrução using:

C#

using System.Data.Common;

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

In CoursesController.cs , add UpdateCourseCredits methods for HttpGet and HttpPost:

C#

public IActionResult UpdateCourseCredits()


{
return View();
}
C#

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

Quando o controlador processa uma solicitação HttpGet, nada é retornado em


ViewData["RowsAffected"] , e a exibição mostra uma caixa de texto vazia e um botão
Enviar, conforme mostrado na ilustração anterior.

Quando o botão Atualizar recebe um clique, o método HttpPost é chamado e


multiplicador tem o valor inserido na caixa de texto. Em seguida, o código executa o
SQL que atualiza os cursos e retorna o número de linhas afetadas para a exibição em
ViewData . Quando a exibição obtém um valor RowsAffected , ela mostra o número de
linhas atualizadas.

Em Gerenciador de Soluções, clique com o botão direito do mouse na pasta


Exibições/Cursos e clique em Adicionar > Novo Item.

Na caixa de diálogo Adicionar Novo Item, clique em ASP.NET Core em Instalado no


painel esquerdo, clique Razor em Exibir e nomeie o novo modo de
exibição UpdateCourseCredits.cshtml .

In Views/Courses/UpdateCourseCredits.cshtml , substitua o código do modelo pelo


seguinte código:

CSHTML

@{
ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

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


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

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


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

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


Clique em Voltar para a Lista para ver a lista de cursos com o número revisado de
créditos.

Observe que o código de produção deve garantir que as atualizações sempre resultem
em dados válidos. O código simplificado mostrado aqui pode multiplicar o número de
créditos o suficiente para resultar em números maiores que 5. (A Credits propriedade
tem um [Range(0, 5)] atributo.) A consulta de atualização funcionaria, mas os dados
inválidos podem causar resultados inesperados em outras partes do sistema que
pressupõem que o número de créditos seja 5 ou menos.

Para obter mais informações sobre consultas SQL brutas, consulte Consultas SQL brutas.

Examinar consultas SQL


Às vezes, é útil poder ver as consultas SQL reais que são enviadas ao banco de dados. A
funcionalidade de log interno para ASP.NET Core é usada EF Core automaticamente
para gravar logs que contêm o SQL para consultas e atualizações. Nesta seção, você
verá alguns exemplos de log do SQL.

Abra StudentsController.cs e, no Details método, defina um ponto de interrupção na


instrução if (student == null) .

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

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

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

Você observará algo aqui que pode ser surpreendente: o SQL seleciona até 2 linhas
( TOP(2) ) da tabela Person. O método SingleOrDefaultAsync não é resolvido para uma 1
linha no servidor. Eis o motivo:

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.

Criar uma camada de abstração


Muitos desenvolvedores escrevem um código para implementar padrões de repositório
e unidade de trabalho como um wrapper em torno do código que funciona com o
Entity Framework. Esses padrões destinam-se a criar uma camada de abstração entre a
camada de acesso a dados e a camada da lógica de negócios de um aplicativo. A
implementação desses padrões pode ajudar a isolar o aplicativo de alterações no
armazenamento de dados e pode facilitar o teste de unidade automatizado ou TDD
(desenvolvimento orientado por testes). No entanto, escrever um código adicional para
implementar esses padrões nem sempre é a melhor escolha para aplicativos que usam o
EF, por vários motivos:
A própria classe de contexto do EF isola o código de código específico a um
armazenamento de dados.

A classe de contexto do EF pode atuar como uma classe de unidade de trabalho


para as atualizações de banco de dados feitas com o EF.

O EF inclui recursos para implementar o TDD sem escrever um código de


repositório.

Para obter informações sobre como implementar os padrões de repositório e unidade


de trabalho, consulte a versão do Entity Framework 5 desta série de tutoriais.

O Entity Framework Core implementa um provedor de banco de dados em memória


que pode ser usado para teste. Para obter mais informações, confira Testar com
InMemory.

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


O Entity Framework determina como uma entidade foi alterada (e, portanto, quais
atualizações precisam ser enviadas ao banco de dados), comparando os valores atuais
de uma entidade com os valores originais. Os valores originais são armazenados
quando a entidade é consultada ou anexada. Alguns dos métodos que causam a
detecção automática de alterações são os seguintes:

DbContext.SaveChanges

DbContext.Entry

ChangeTracker.Entries

Se você estiver controlando um grande número de entidades e chamar um desses


métodos muitas vezes em um loop, poderá obter melhorias significativas de
desempenho desativando temporariamente a detecção automática de alterações
usando a propriedade ChangeTracker.AutoDetectChangesEnabled . Por exemplo:

C#

_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core código-fonte e planos de


desenvolvimento
O código-fonte do Entity Framework Core está em https://github.com/dotnet/efcore .
O EF Core repositório contém builds noturnos, acompanhamento de problemas,
especificações de recursos, notas de reunião de design e o roteiro para
desenvolvimento futuro . Arquive ou encontre bugs e contribua.

Embora o código-fonte seja aberto, há suporte completo para o Entity Framework Core
como um produto Microsoft. A equipe do Microsoft Entity Framework mantém controle
sobre quais contribuições são aceitas e testa todas as alterações de código para garantir
a qualidade de cada versão.

Fazer engenharia reversa do banco de dados


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

Usar o LINQ dinâmico para simplificar o código


O terceiro tutorial desta série mostra como escrever um código LINQ embutindo nomes
de colunas em código em uma instrução switch . Com duas colunas para escolha, isso
funciona bem, mas se você tiver muitas colunas, o código poderá ficar detalhado. Para
resolver esse problema, use o método EF.Property para especificar o nome da
propriedade como uma cadeia de caracteres. Para usar essa abordagem, substitua o
método Index no StudentsController pelo código a seguir.

C#

public async Task<IActionResult> Index(


string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] =
String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
ViewData["DateSortParm"] =
sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" :
"EnrollmentDate";

if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}

ViewData["CurrentFilter"] = searchString;

var students = from s in _context.Students


select s;

if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}

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

bool descending = false;


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

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

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

Agradecimentos
Tom Dykstra e Rick Anderson (twitter @RickAndMSFT) escreveu 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. John Parente e Paul Goldman trabalharam na atualização
do tutorial do ASP.NET Core 2.2.

Solucionar erros comuns

ContosoUniversity.dll usada por outro processo


Mensagem de erro:

Não é possível abrir '...bin\Debug\netcoreapp1.0\ContosoUniversity.dll' para


gravação – 'O processo não pode acessar o arquivo
'...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' porque ele está sendo usado
por outro processo.

Solução:

Pare o site no IIS Express. Acesse a Bandeja do Sistema do Windows, localize o IIS
Express e clique com o botão direito do mouse em seu ícone, selecione o site da
Contoso University e, em seguida, clique em Parar Site.

Migração gerada por scaffolding sem nenhum código


nos métodos Up e Down
Possível causa:

Os comandos da CLI do EF não fecham e salvam arquivos de código automaticamente.


Se você tiver alterações não salvas ao executar o comando migrations add , o EF não
encontrará as alterações.

Solução:

Execute o comando migrations remove , salve as alterações de código e execute o


comando migrations add novamente.

Erros durante a execução da atualização de banco de


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

A abordagem mais simples é renomear o banco de dados em appsettings.json . Na


próxima vez que você executar database update , um novo banco de dados será criado.

Para excluir um banco de dados no SSOX, clique com o botão direito do mouse no
banco de dados, clique Excluir e, em seguida, na caixa de diálogo Excluir Banco de
Dados, selecione Fechar conexões existentes e clique em OK.

Para excluir um banco de dados usando a CLI, execute o comando database drop da
CLI:

CLI do .NET

dotnet ef database drop

Erro ao localizar a instância do SQL Server


Mensagem de erro:

Ocorreu um erro relacionado à rede ou específico da 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 de instância está correto e se o SQL Server está configurado
para permitir conexões remotas. (provedor: Adaptadores de Rede do SQL, erro: 26 –
Erro ao Localizar Servidor/Instância Especificada)

Solução:

Verifique a cadeia de conexão. Se você excluiu o arquivo de banco de dados


manualmente, altere o nome do banco de dados na cadeia de caracteres de construção
para começar novamente com um novo banco de dados.

Obter o código
Baixe ou exiba o aplicativo concluído.

Recursos adicionais
Para obter mais informações sobre EF Core, consulte a documentação do Entity
Framework Core. Um manual também está disponível: Entity Framework Core in
Action (Entity Framework Core em ação).

Para obter informações sobre como implantar um aplicativo Web, consulte Hospedar e
implantar ASP.NET Core.

Para obter informações sobre outros tópicos relacionados a ASP.NET Core MVC, como
autenticação e autorização, consulte a visão geral de ASP.NET Core.

Próximas etapas
Neste tutorial, você:

" Executou consultas SQL brutas


" Chamou uma consulta para retornar entidades
" Chamou uma consulta para outros tipos
" Chamou uma consulta de atualização
" Examinou consultas SQL
" Criou uma camada de abstração
" Aprendeu sobre a Detecção automática de alterações
" Saiba mais sobre EF Core o código-fonte e os planos de desenvolvimento
" Aprendeu a usar o LINQ dinâmico para simplificar o código

Isso conclui esta série de tutoriais sobre como usar o Entity Framework Core em um
aplicativo ASP.NET Core MVC. Esta série funcionou com um novo banco de dados; uma
alternativa é fazer engenharia reversa de um modelo de um banco de dados existente.

Tutorial: EF Core com o MVC, banco de dados existente


Visão geral de conceitos básicos do
ASP.NET Core
Artigo • 28/11/2022 • 19 minutos para o fim da leitura

Conheça os conceitos fundamentais para criar aplicativos ASP.NET Core, incluindo DI


(injeção de dependência), configuração, middleware e muito mais.

Module.vb
Aplicativos ASP.NET Core criados com modelos Web contêm o código de inicialização
do aplicativo no arquivo Program.cs . O arquivo Program.cs é onde:

Os serviços exigidos pelo aplicativo são configurados.


O pipeline de tratamento de solicitações do aplicativo é definido como uma série
de componentes de middleware.

O seguinte código de inicialização do aplicativo dá suporte a:

Razor Pages
Controladores MVC com exibições
API Web com controladores
APIs Web mínimas

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();
app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

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


O ASP.NET Core inclui injeção de dependência (DI) que disponibiliza os serviços
configurados em todo o aplicativo. Os serviços são adicionados ao contêiner de DI com
WebApplicationBuilder.Services, builder.Services , no código anterior. Quando a
instância de WebApplicationBuilder é criada, muitos serviços fornecidos pela estrutura
são adicionados. builder é um WebApplicationBuilder no seguinte código:

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

No código realçado anterior, builder tem a configuração, o registro em log e muitos


outros serviços adicionados ao contêiner de DI.

O seguinte código adiciona Razor Pages, controladores MVC com exibições e um


DbContext personalizado ao contêiner de DI:

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RPMovieConte
xt")));

var app = builder.Build();


Normalmente, os serviços são resolvidos da DI usando injeção de construtor. A
estrutura de DI fornece uma instância desse serviço em runtime.

O seguinte código usa a injeção de construtor para resolver o contexto do banco de


dados e o agente de DI:

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovieContext _context;
private readonly ILogger<IndexModel> _logger;

public IndexModel(RazorPagesMovieContext context, ILogger<IndexModel>


logger)
{
_context = context;
_logger = logger;
}

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

public async Task OnGetAsync()


{
_logger.LogInformation("IndexModel OnGetAsync.");
Movie = await _context.Movie.ToListAsync();
}
}

Middleware
O pipeline de tratamento de solicitação é composto como uma série de componentes
de middleware. Cada componente executa operações em um HttpContext e invoca o
próximo middleware no pipeline ou encerra a solicitação.

Por convenção, um componente de middleware é adicionado ao pipeline invocando um


método de extensão Use{Feature} . O middleware adicionado ao aplicativo está
realçado no seguinte código:

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();


// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Para obter mais informações, confira Middleware do ASP.NET Core.

Host
Na inicialização, um aplicativo ASP.NET Core cria um host. O host encapsula todos os
recursos do aplicativo, como:

Uma implementação do servidor HTTP


Componentes de middleware
Log
Serviço de DI (injeção de dependência)
Configuração

Há três hosts diferentes:

.NET WebApplication Host, também conhecido como Host Mínimo.


Host Genérico .NET
Host da Web do ASP.NET Core

O Host .NET WebApplication é recomendado e usado em todos os modelos do ASP.NET


Core. O Host .NET WebApplication e o Host Genérico do .NET compartilham muitas das
mesmas interfaces e classes. O Host da Web do ASP.NET Core está disponível apenas
para compatibilidade com versões anteriores.

O seguinte exemplo cria uma instância de um Host do WebApplication:

C#
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

O método WebApplicationBuilder.Build configura um host com um conjunto de opções


padrão, como:

Usar o Kestrel como o servidor Web e habilitar a integração do IIS.


Carregar a configuração de appsettings.json , variáveis de ambiente, argumentos
de linha de comando e de outras fontes de configuração.
Envio da saída de log para os provedores de console e de depuração.

Cenários não Web


O Host Genérico permite que outros tipos de aplicativos usem extensões de estruturas
abrangentes como registro em log, DI (Injeção de Dependência), configuração e
gerenciamento do tempo de vida dos aplicativos. Para obter mais informações, consulte
Host Genérico do .NET no ASP.NET Core e Tarefas em segundo plano com serviços
hospedados no ASP.NET Core.

Servidores
Um aplicativo ASP.NET Core usa uma implementação do servidor HTTP para ouvir
solicitações HTTP. O servidor descobre solicitações ao aplicativo como um conjunto de
recursos de solicitação compostos em um HttpContext .

Windows

O ASP.NET Core vem com as seguintes implementações de servidor:

O Kestrel é um servidor Web multiplataforma. O Kestrel normalmente é


executado em uma configuração de proxy reverso que usa o IIS . No
ASP.NET Core 2.0 ou posterior, o Kestrel também pode ser executado como
um servidor de borda voltado para o público exposto diretamente à Internet.
O Servidor HTTP de IIS é um servidor do Windows que usa o IIS. Com esse
servidor, o aplicativo ASP.NET Core e o IIS são executados no mesmo
processo.
HTTP.sys é um servidor para Windows que não é usado com IIS.
Para obter mais informações, consulte Implementações do servidor Web no ASP.NET
Core.

Configuração
O ASP.NET Core fornece uma estrutura de configuração que obtém as configurações
como pares nome-valor de um conjunto ordenado de provedores de configuração.
Provedores de configuração internos estçao disponíveis para uma variedade de fontes,
como arquivos .json , arquivos .xml , variáveis de ambiente e argumentos de linha de
comando. Escreva provedores de configuração personalizados para dar suporte a outras
fontes.

Por padrão, aplicativos ASP.NET Core são configurados para leitura de


appsettings.json , variáveis de ambiente, linha de comando e muito mais. Quando a
configuração do aplicativo é carregada, os valores das variáveis de ambiente substituem
valores de appsettings.json .

Para gerenciar dados de configuração confidenciais como senhas, o .NET Core fornece
um Gerenciador de Segredos. Para segredos de produção, recomendamos o Azure Key
Vault.

Para obter mais informações, consulte Configuração no ASP.NET Core.

Ambientes
Ambientes de execução, como Development , Staging e Production , estão disponíveis no
ASP.NET Core. Especifique o ambiente em que um aplicativo está em execução
definindo a variável de ambiente ASPNETCORE_ENVIRONMENT . O ASP.NET Core lê a variável
de ambiente na inicialização do aplicativo e armazena o valor em uma implementação
IWebHostEnvironment . Essa implementação está disponível em qualquer lugar de um
aplicativo por meio da DI (injeção de dependência).

O seguinte exemplo configura o manipulador de exceções e o middleware HSTS


(Protocolo de Segurança de Transporte Estrito HTTP) quando não é executado no
ambiente Development :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Para obter mais informações, confira Usar vários ambientes no ASP.NET Core.

Log
O ASP.NET Core dá suporte a uma API de registro em log que funciona com uma série
de provedores de registro em log internos e de terceiros. Os provedores disponíveis
incluem:

Console
Depurar
Rastreamento de Eventos no Windows
Log de eventos do Windows
TraceSource
Serviço de aplicativo do Azure
Azure Application Insights

Para criar logs, resolva um serviço ILogger<TCategoryName> com base na DI (injeção


de dependência) e chame métodos de registro em log, como LogInformation. Por
exemplo:

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovieContext _context;
private readonly ILogger<IndexModel> _logger;

public IndexModel(RazorPagesMovieContext context, ILogger<IndexModel>


logger)
{
_context = context;
_logger = logger;
}

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

public async Task OnGetAsync()


{
_logger.LogInformation("IndexModel OnGetAsync.");
Movie = await _context.Movie.ToListAsync();
}
}

Para obter mais informações, confira Log no .NET Core e no ASP.NET Core.

Roteamento
Um rota é um padrão de URL mapeado para um manipulador. O manipulador
normalmente é um Razor Page, um método de ação em um controlador MVC ou um
middleware. O roteamento do ASP.NET Core lhe dá controle sobre as URLs usadas pelo
seu aplicativo.

O seguinte código, gerado pelo modelo de aplicativo Web ASP.NET Core, chama
UseRouting:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

Saiba mais em Roteamento no ASP.NET Core.

Tratamento de erros
O ASP.NET Core tem recursos internos para tratamento de erros, como:

Uma página de exceção do desenvolvedor


Páginas de erro personalizadas
Páginas de código de status estático
Tratamento de exceção na inicialização

Para obter mais informações, confira Manipular erros no ASP.NET Core.

Fazer solicitações HTTP


Uma implementação de IHttpClientFactory está disponível para a criação de instâncias
do HttpClient . O alocador:

Fornece um local central para nomear e configurar instâncias lógicas de


HttpClient . Por exemplo, registre e configure um cliente do github para acessar o
GitHub. Registre e configure um cliente padrão para outras finalidades.
Dá suporte ao registro e ao encadeamento de vários manipuladores de delegação
para criar um pipeline do middleware de solicitação saída. Esse padrão é
semelhante ao pipeline do middleware de entrada no ASP.NET Core. O padrão
fornece um mecanismo para gerenciar interesses paralelos em relação às
solicitações HTTP, incluindo o armazenamento em cache, o tratamento de erro, a
serialização e o registro em log.
Integra-se com a Polly, uma biblioteca de terceiros popular para tratamento de
falhas transitórias.
Gerencia o pooling e o tempo de vida das instâncias de HttpClientHandler
subjacentes para evitar problemas de DNS comuns que ocorrem no
gerenciamento manual de tempos de vida de HttpClient .
Adiciona uma experiência de registro em log configurável via ILogger para todas
as solicitações enviadas por meio de clientes criados pelo alocador.
Para saber mais, confira Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET
Core.

Raiz do conteúdo
A raiz do conteúdo é o caminho base para:

O executável que hospeda o aplicativo (.exe).


Assemblies compilados que compõem o aplicativo (.dll).
Arquivos de conteúdo usados pelo aplicativo, como:
Arquivos Razor ( .cshtml , .razor )
Arquivos de configuração ( .json , .xml )
Arquivos de dados ( .db )
A raiz da Web, normalmente a pasta wwwroot.

Durante o desenvolvimento, a raiz do conteúdo é padrão para o diretório raiz do


projeto. Esse diretório também é o caminho base para os arquivos de conteúdo do
aplicativo e a raiz da Web. Especifique uma raiz de conteúdo diferente definindo seu
caminho ao criar o host. Para obter mais informações, veja Raiz de conteúdo.

Raiz da Web
A raiz da Web é o caminho base para arquivos de recursos públicos e estáticos, como:

Folhas de estilos ( .css )


JavaScript ( .js )
Imagens ( .png , .jpg )

Por padrão, arquivos estáticos são fornecidos apenas do diretório raiz da Web e seus
subdiretórios. O caminho raiz da Web é padrão para {raiz de conteúdo}/wwwroot.
Especifique uma raiz da Web diferente definindo seu caminho ao criar o host. Para obter
mais informações, confira Diretório base.

Impeça a publicação de arquivos em wwwroot com o Item de projeto de <conteúdo>


no arquivo de projeto. O seguinte exemplo impede a publicação de conteúdo em
wwwroot/local e seus subdiretórios:

XML

<ItemGroup>
<Content Update="wwwroot\local\**\*.*" CopyToPublishDirectory="Never" />
</ItemGroup>
Nos arquivos Razor .cshtml , ~/ aponta para a raiz da Web. Um caminho que começa
com ~/ é conhecido como um caminho virtual.

Saiba mais em Arquivos estáticos no ASP.NET Core.

Recursos adicionais
Código-fonte do WebApplicationBuilder
Inicialização de aplicativo no ASP.NET
Core
Artigo • 28/11/2022 • 8 minutos para o fim da leitura

De Rick Anderson

Aplicativos ASP.NET Core criados com modelos Web contêm o código de inicialização
do aplicativo no arquivo Program.cs .

O seguinte código de inicialização do aplicativo dá suporte a:

Razor Pages
Controladores MVC com exibições
API Web com controladores
APIs mínimas

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Os aplicativos que usam o EventSource podem medir o tempo de inicialização para


entender e otimizar o desempenho da inicialização. O ServerReady evento representa
o ponto em Microsoft.AspNetCore.Hosting que o servidor está pronto para responder
às solicitações.

Para obter mais informações sobre a inicialização do aplicativo, consulte ASP.NET Core
visão geral dos conceitos básicos.
Injeção de dependência no ASP.NET
Core
Artigo • 28/11/2022 • 33 minutos para o fim da leitura

Por Kirk Larkin , Steve Smith e Brandon Dahler

O ASP.NET Core é compatível com o padrão de design de software de DI (injeção de


dependência), que é uma técnica para alcançar a IoC (Inversão de Controle) entre
classes e suas dependências.

Para obter mais informações específicas da injeção de dependência em controladores


MVC, consulte a injeção de dependência em controladores em ASP.NET Core.

Para obter informações sobre como usar a injeção de dependência em aplicativos


diferentes de aplicativos Web, consulte a injeção de dependência no .NET.

Para obter mais informações sobre a injeção de dependência de opções, consulte o


padrão Opções em ASP.NET Core.

Este tópico fornece informações sobre injeção de dependência em ASP.NET Core. A


documentação primária sobre como usar a injeção de dependência está contida na
injeção de dependência no .NET.

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

Visão geral da injeção de dependência


Uma dependência é um objeto do qual outro objeto depende. Examine a classe
MyDependency a seguir com um método WriteMessage do qual outras classes em um

aplicativo dependem:

C#

public class MyDependency


{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message:
{message}");
}
}
Uma classe pode criar uma instância da classe MyDependency para usar seu método
WriteMessage . No exemplo a seguir, a classe MyDependency é uma dependência da classe
IndexModel :

C#

public class IndexModel : PageModel


{
private readonly MyDependency _dependency = new MyDependency();

public void OnGet()


{
_dependency.WriteMessage("IndexModel.OnGet");
}
}

A classe cria e depende diretamente da instância MyDependency . As dependências de


código, como no exemplo anterior, são problemáticas e devem ser evitadas pelos
seguintes motivos:

Para substituir MyDependency por uma implementação diferente, a classe


IndexModel deve ser modificada.

Se MyDependency tiver dependências, elas também deverão ser configuradas pela


classe IndexModel . Em um projeto grande com várias classes dependendo da
MyDependency , o código de configuração fica pulverizado por todo o aplicativo.
É difícil testar a unidade dessa implementação.

Injeção de dependência trata desses problemas da seguinte maneira:

O uso de uma interface ou classe base para abstrair a implementação da


dependência.
Registrando a dependência em um contêiner de serviço. O ASP.NET Core fornece
um contêiner de serviço interno, o IServiceProvider. Normalmente, os serviços são
registrados no arquivo do Program.cs aplicativo.
Injeção do serviço no construtor da classe na qual ele é usado. A estrutura assume
a responsabilidade de criar uma instância da dependência e de descartá-la quando
não for mais necessária.

No aplicativo de exemplo , a IMyDependency interface define o WriteMessage método:

C#

public interface IMyDependency


{
void WriteMessage(string message);
}

Essa interface é implementada por um tipo concreto, MyDependency :

C#

public class MyDependency : IMyDependency


{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}

O aplicativo de exemplo registra o IMyDependency serviço com o tipo


MyDependency concreto. O método AddScoped registra o serviço com um tempo de vida

com escopo, o tempo de vida de uma única solicitação. Descreveremos posteriormente


neste tópico os tempos de vida do serviço.

C#

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

No exemplo de aplicativo, o serviço IMyDependency é solicitada e usada para chamar o


método WriteMessage :

C#

public class Index2Model : PageModel


{
private readonly IMyDependency _myDependency;

public Index2Model(IMyDependency myDependency)


{
_myDependency = myDependency;
}

public void OnGet()


{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}

Usando o padrão DI, o controlador ou Razor a página:

Não usa o tipo MyDependency concreto, apenas a IMyDependency interface que ele
implementa. Isso facilita a alteração da implementação sem modificar o
controlador ou Razor a página.
Não cria uma instância da MyDependency qual ela é criada pelo contêiner de DI.

A implementação da interface IMyDependency pode ser aprimorada usando a API de log


interna:

C#

public class MyDependency2 : IMyDependency


{
private readonly ILogger<MyDependency2> _logger;

public MyDependency2(ILogger<MyDependency2> logger)


{
_logger = logger;
}

public void WriteMessage(string message)


{
_logger.LogInformation( $"MyDependency2.WriteMessage Message:
{message}");
}
}

O atualizado Program.cs registra a nova IMyDependency implementação:

C#

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();


MyDependency2 depende ILogger<TCategoryName>do que ele solicita no construtor.

ILogger<TCategoryName> é um serviço fornecido pela estrutura.

Não é incomum usar a injeção de dependência de uma maneira encadeada. Por sua vez,
cada dependência solicitada solicita suas próprias dependências. O contêiner resolve as
dependências no grafo e retorna o serviço totalmente resolvido. O conjunto de
dependências que precisa ser resolvido normalmente é chamado de árvore de
dependência, grafo de dependência ou grafo de objeto.

O contêiner resolve ILogger<TCategoryName> aproveitando os tipos abertos (genéricos),


eliminando a necessidade de registrar todo tipo construído (genérico).

Na terminologia de injeção de dependência, um serviço:

Normalmente, é um objeto que fornece um serviço para outros objetos, como o


serviço IMyDependency .
Não está relacionado a um serviço Web, embora o serviço possa usar um serviço
Web.

A estrutura fornece um sistema de log robusto. As implementações IMyDependency


mostradas nos exemplos anteriores foram gravadas para demonstrar a DI básica, não
para implementar o registro em log. A maioria dos aplicativos não deve precisar
escrever agentes. O código a seguir demonstra o uso do log padrão, que não exige que
nenhum serviço seja registrado:

C#

public class AboutModel : PageModel


{
private readonly ILogger _logger;

public AboutModel(ILogger<AboutModel> logger)


{
_logger = logger;
}

public string Message { get; set; } = string.Empty;

public void OnGet()


{
Message = $"About page visited at
{DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Usando o código anterior, não é necessário atualizar Program.cs , pois o log é fornecido
pela estrutura.

Registrar grupos de serviços com métodos de


extensão
A estrutura ASP.NET Core usa uma convenção para registrar um grupo de serviços
relacionados. A convenção é usar um único método de extensão Add{GROUP_NAME} para
registrar todos os serviços exigidos por um recurso de estrutura. Por exemplo, o
AddControllers método de extensão registra os serviços necessários para controladores
MVC.

O código a seguir é gerado pelo modelo de Razor Páginas usando contas de usuário
individuais e mostra como adicionar serviços adicionais ao contêiner usando os
métodos AddDbContext de extensão e AddDefaultIdentity:

C#

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Considere o seguinte ao registrar serviços e configurar opções:

C#

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Grupos de registros relacionados podem ser movidos para um método de extensão


para registrar serviços. Por exemplo, os serviços de configuração são adicionados à
seguinte classe:

C#

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));

return services;
}

public static IServiceCollection AddMyDependencyGroup(


this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();

return services;
}
}
}

Os serviços restantes são registrados em uma classe similar. O código a seguir usa os
novos métodos de extensão para registrar os serviços:

C#
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Observação: Cada método de extensão services.Add{GROUP_NAME} adiciona e


possivelmente configura serviços. Por exemplo, AddControllersWithViews adiciona os
serviços que os controladores MVC com visualizações requerem e AddRazorPages
adiciona os serviços que o Razor Pages requer.

Tempos de vida do serviço


Ver tempo de vida do serviço na injeção de dependência no .NET

Para usar serviços com escopo no middleware, use uma das seguintes abordagens:

Injete o serviço no método ou InvokeAsync no Invoke middleware. O uso de


injeção de construtor gera uma exceção de runtime porque força o serviço com
escopo a se comportar como um singleton. O exemplo na seção De tempo de vida
e opções de registro demonstra a InvokeAsync abordagem.
Use middleware baseado em Fábrica. O middleware registrado usando essa
abordagem é ativado por solicitação de cliente (conexão), o que permite que
serviços com escopo sejam injetados no construtor do middleware.

Para obter mais informações, confira Escrever middleware do ASP.NET Core


personalizado.

Métodos de registro do serviço


Consulte os métodos de registro de serviço na injeção de dependência no .NET

É comum usar várias implementações ao zombar de tipos para teste.

Registrar um serviço com apenas um tipo de implementação equivale a registrar esse


serviço com o mesmo tipo de implementação e serviço. É por isso que várias
implementações de um serviço não podem ser registradas usando os métodos que não
usam um tipo de serviço explícito. Esses métodos podem registrar várias instâncias de
um serviço, mas todos eles terão o mesmo tipo de implementação.

Qualquer um dos métodos de registro de serviço acima pode ser usado para registrar
várias instâncias de serviço do mesmo tipo de serviço. No exemplo a seguir,
AddSingleton é chamado duas vezes com IMyDependency como tipo de serviço. A

segunda chamada para AddSingleton substitui a anterior quando resolvida como


IMyDependency e adiciona à anterior quando vários serviços são resolvidos por meio de
IEnumerable<IMyDependency> . Os serviços aparecem na ordem em que foram registrados

quando resolvidos por meio de IEnumerable<{SERVICE}> .

C#

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService


{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);

var dependencyArray = myDependencies.ToArray();


Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}

Comportamento da injeção de construtor


Consulte o comportamento de injeção de construtor na injeção de dependência no .NET

Contextos de Entity Framework


Por padrão, os contextos do Entity Framework são adicionados ao contêiner de serviço
usando o tempo de vida com escopo porque as operações de banco de dados do
aplicativo Web normalmente têm o escopo da solicitação do cliente. Para usar um
tempo de vida diferente, especifique o tempo de vida usando uma AddDbContext
sobrecarga. Os serviços de um determinado tempo de vida não devem usar um
contexto de banco de dados com um tempo de vida menor que o tempo de vida do
serviço.
Opções de tempo de vida e de registro
Para demonstrar a diferença entre o tempo de vida do serviço e suas opções de registro,
considere as interfaces a seguir que representam uma tarefa como uma operação com
um identificador. OperationId Dependendo de como o tempo de vida do serviço de
uma operação é configurado para as seguintes interfaces, o contêiner fornece as
mesmas ou diferentes instâncias do serviço quando solicitado por uma classe:

C#

public interface IOperation


{
string OperationId { get; }
}

public interface IOperationTransient : IOperation { }


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

A classe a seguir Operation implementa todas as interfaces anteriores. O Operation


construtor gera um GUID e armazena os últimos 4 caracteres na OperationId
propriedade:

C#

public class Operation : IOperationTransient, IOperationScoped,


IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}

public string OperationId { get; }


}

O código a seguir cria vários registros da Operation classe de acordo com os tempos de
vida nomeados:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O aplicativo de exemplo demonstra o tempo de vida do objeto dentro e entre


solicitações. O IndexModel middleware e o middleware solicitam cada tipo de
IOperation tipo e registram o OperationId log para cada:

C#

public class IndexModel : PageModel


{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;

public IndexModel(ILogger<IndexModel> logger,


IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}

public void OnGet()


{
_logger.LogInformation("Transient: " +
_transientOperation.OperationId);
_logger.LogInformation("Scoped: " +
_scopedOperation.OperationId);
_logger.LogInformation("Singleton: " +
_singletonOperation.OperationId);
}
}

Semelhante ao IndexModel middleware, o middleware resolve os mesmos serviços:

C#

public class MyMiddleware


{
private readonly RequestDelegate _next;
private readonly ILogger _logger;

private readonly IOperationSingleton _singletonOperation;

public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,


IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
_next = next;
}

public async Task InvokeAsync(HttpContext context,


IOperationTransient transientOperation, IOperationScoped
scopedOperation)
{
_logger.LogInformation("Transient: " +
transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " +
_singletonOperation.OperationId);

await _next(context);
}
}

public static class MyMiddlewareExtensions


{
public static IApplicationBuilder UseMyMiddleware(this
IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}

Os serviços com escopo e transitórios devem ser resolvidos no InvokeAsync método:

C#
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped
scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

await _next(context);
}

A saída do agente mostra:

Os objetos transitórios sempre são diferentes. O valor transitório OperationId é


diferente no IndexModel middleware e no middleware.
Objetos com escopo são os mesmos para uma determinada solicitação, mas
diferem em cada nova solicitação.
Objetos Singleton são os mesmos para cada solicitação.

Para reduzir a saída de log, defina "Log:LogLevel:Microsoft:Error" no


appsettings.Development.json arquivo:

JSON

{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}

Resolver um serviço na inicialização do


aplicativo
O código a seguir mostra como resolver um serviço com escopo por uma duração
limitada quando o aplicativo é iniciado:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())


{
var services = serviceScope.ServiceProvider;

var myDependency = services.GetRequiredService<IMyDependency>();


myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Validação de escopo
Consulte o comportamento de injeção de construtor na injeção de dependência no .NET

Para obter mais informações, confira Validação de escopo.

Serviços de solicitação
Os serviços e suas dependências dentro de uma solicitação ASP.NET Core são expostos
por meio HttpContext.RequestServicesde .

A estrutura cria um escopo por solicitação e RequestServices expõe o provedor de


serviços com escopo. Todos os serviços com escopo são válidos enquanto a solicitação
estiver ativa.

7 Observação

Prefira solicitar dependências como parâmetros de construtor em vez de resolver


serviços de RequestServices . Solicitar dependências como parâmetros de
construtor gera classes mais fáceis de testar.

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


Ao criar serviços para injeção de dependência:

Evite membros e classes estáticos com estado. Evite criar um estado global
projetando aplicativos para usar serviços singleton.
Evite a instanciação direta das classes dependentes em serviços. A instanciação
direta acopla o código a uma implementação específica.
Deixe os serviços pequenos, bem fatorados e fáceis de serem testados.

Se uma classe tiver muitas dependências injetadas, pode ser um sinal de que a classe
tem muitas responsabilidades e viola o SRP (Princípio de Responsabilidade Única). Tente
refatorar a classe movendo algumas das responsabilidades para uma nova classe. Tenha
em mente que Razor as classes de modelo de página de Páginas e classes de
controlador MVC devem se concentrar nas preocupações da interface do usuário.

Descarte de serviços
O contêiner chama Dispose para os tipos IDisposable criados por ele. Os serviços
resolvidos do contêiner nunca devem ser descartados pelo desenvolvedor. Se um tipo
ou um alocador for registrado como singleton, o contêiner descartará o singleton
automaticamente.

No exemplo a seguir, os serviços são criados pelo contêiner de serviço e descartados


automaticamente: dependency-injection\samples\6.x\DIsample2\Services\Service1.cs

C#

public class Service1 : IDisposable


{
private bool _disposed;

public void Write(string message)


{
Console.WriteLine($"Service1: {message}");
}

public void Dispose()


{
if (_disposed)
return;

Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}

public class Service2 : IDisposable


{
private bool _disposed;

public void Write(string message)


{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;

Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}

public interface IService3


{
public void Write(string message);
}

public class Service3 : IService3, IDisposable


{
private bool _disposed;

public Service3(string myKey)


{
MyKey = myKey;
}

public string MyKey { get; }

public void Write(string message)


{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}

public void Dispose()


{
if (_disposed)
return;

Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}

C#

using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();

C#

public class IndexModel : PageModel


{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;

public IndexModel(Service1 service1, Service2 service2, IService3


service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}

public void OnGet()


{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}

O console de depuração mostra a seguinte saída após cada atualização da página


Índice:

Console

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet
Service1.Dispose

Serviços não criados pelo contêiner de serviço


Considere o seguinte código:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

No código anterior:

As instâncias de serviço não são criadas pelo contêiner de serviço.


A estrutura não descarta os serviços automaticamente.
O desenvolvedor é responsável por descartar os serviços.

Diretrizes idisposáveis para instâncias transitórias e


compartilhadas
Consulte as diretrizes IDisposable para instância transitória e compartilhada na injeção
de dependência no .NET

Substituição do contêiner de serviço padrão


Consulte a substituição padrão do contêiner de serviço na injeção de dependência no
.NET

Recomendações
Consulte recomendações na injeção de dependência no .NET

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:

Incorreto:

Correto:
C#

public class MyClass


{
private readonly IOptionsMonitor<MyOptions> _optionsMonitor;

public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)


{
_optionsMonitor = optionsMonitor;
}

public void MyMethod()


{
var option = _optionsMonitor.CurrentValue.Option;

...
}
}

Outra variação de localizador de serviço a ser evitada é injetar um alocador que


resolve as dependências em runtime. Essas duas práticas misturam estratégias de
inversão de controle.

Evite o acesso estático a HttpContext (por exemplo,


IHttpContextAccessor.HttpContext).

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.

Padrões recomendados para várias locações no


DI
O Orchard Core é uma estrutura de aplicativos para a criação de aplicativos
modulares multilocatários em ASP.NET Core. Para obter mais informações, consulte a
Documentação do Orchard Core .

Consulte os exemplos do Orchard Core para obter exemplos de como criar aplicativos
modulares e multilocatários usando apenas o Orchard Core Framework sem nenhum de
seus recursos específicos do CMS.

Serviços fornecidos pela estrutura


Program.cs registra serviços que o aplicativo usa, incluindo recursos de plataforma,

como Entity Framework Core e ASP.NET Core MVC. Inicialmente, o IServiceCollection


fornecido para Program.cs possui serviços definidos pela estrutura dependendo de
como o host foi configurado. Para aplicativos com base nos modelos de ASP.NET Core,
a estrutura registra mais de 250 serviços.

A tabela a seguir lista um pequeno exemplo desses serviços registrados por estrutura:

Tipo de Serviço Tempo de vida

Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transitório

IHostApplicationLifetime Singleton

IWebHostEnvironment Singleton

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

Microsoft.Extensions.Logging.ILoggerFactory Singleton

Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton

Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transitório

Microsoft.Extensions.Options.IOptions<TOptions> Singleton

System.Diagnostics.DiagnosticSource Singleton

System.Diagnostics.DiagnosticListener Singleton

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 requisitos no ASP.NET Core
Blazor ASP.NET Core injeção de dependência
Padrões de conferência NDC para desenvolvimento de aplicativos DI
Inicialização de aplicativo no ASP.NET Core
Ativação de middleware baseada em alocador no ASP.NET Core
Quatro maneiras de descartar IDisposables no ASP.NET Core
Como gravar um código limpo no ASP.NET Core com injeção de dependência
(MSDN)
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)
Como registrar um serviço com várias interfaces na DI do ASP.NET Core
Middleware do ASP.NET Core
Artigo • 05/01/2023 • 47 minutos para o fim da leitura

Por Rick Anderson e Steve Smith

O middleware é um software montado em um pipeline de aplicativo para manipular


solicitações e respostas. Cada componente:

Escolhe se deseja passar a solicitação para o próximo componente no pipeline.


Pode executar o trabalho antes e depois do próximo componente no pipeline.

Os delegados de solicitação são usados para criar o pipeline de solicitação. Os


delegados de solicitação manipulam cada solicitação HTTP.

Os delegados de solicitação são configurados usando os métodos de extensão Run,


Map e Use. Um delegado de solicitação individual pode ser especificado em linha como
um método anônimo (chamado do middleware em linha) ou pode ser definido em uma
classe reutilizável. Essas classes reutilizáveis e os métodos anônimos em linha são o
middleware, também chamado de componentes do middleware. Cada componente de
middleware no pipeline de solicitação é responsável por invocar o próximo componente
no pipeline ou causar um curto-circuito do pipeline. Quando um middleware causa um
curto-circuito, ele é chamado de middleware terminal, porque impede que outros
middlewares processem a solicitação.

Migrar módulos e manipuladores HTTP para o middleware do ASP.NET Core explica a


diferença entre pipelines de solicitação no ASP.NET Core e no ASP.NET 4.x e fornece
mais exemplos do middleware.

Análise de código do middleware


O ASP.NET Core inclui muitos analisadores de plataforma de compilador que
inspecionam o código do aplicativo em busca de qualidade. Para obter mais
informações, consulte Análise de código em aplicativos ASP.NET Core

Criar um pipeline de middleware com


WebApplication
O pipeline de solicitação do ASP.NET Core consiste em uma sequência de delegados de
solicitação, chamados um após o outro. O diagrama a seguir demonstra o conceito. O
thread de execução segue as setas pretas.
Cada delegado pode executar operações antes e depois do próximo delegado. Os
delegados de tratamento de exceção devem ser chamados no início do pipeline para
que possam detectar exceções que ocorrem em etapas posteriores do pipeline.

O aplicativo ASP.NET Core mais simples possível define um delegado de solicitação


única que controla todas as solicitações. Este caso não inclui um pipeline de solicitação
real. Em vez disso, uma única função anônima é chamada em resposta a cada solicitação
HTTP.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Run(async context =>


{
await context.Response.WriteAsync("Hello world!");
});

app.Run();

Encadeie vários delegados de solicitação junto com o Use. O parâmetro next representa
o próximo delegado no pipeline. Lembre-se de que você pode causar um curto-circuito
no pipeline ao não chamar o parâmetro next . Normalmente, você pode executar ações
antes e depois do delegado next , conforme o seguinte exemplo demonstra:

C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>


{
// Do work that can write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>


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

app.Run();

Quando um delegado não transmite uma solicitação ao próximo delegado, considera-se


que ele esteja causando 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 agir com um middleware terminal, processando
uma solicitação para um arquivo estático e causar curto-circuito no restante do pipeline.
O middleware adicionado ao pipeline antes do middleware que finaliza o
processamento adicional ainda processa o código após as instruções next.Invoke . No
entanto, confira o seguinte aviso sobre a tentativa de gravar em uma resposta que já foi
enviada.

2 Aviso

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, 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.
Delegados Run não recebem um parâmetro next . O primeiro delegado Run sempre é
terminal e encerra o pipeline. Run é uma convenção. A alguns componentes de
middleware podem expor os métodos Run[Middleware] que são executados no final do
pipeline:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Use(async (context, next) =>


{
// Do work that can write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>


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

app.Run();

Se quiser ver os comentários de código traduzidos para idiomas diferentes do inglês,


informe-nos neste problema de discussão do GitHub .

No exemplo anterior, o delegado Run grava "Hello from 2nd delegate." na resposta e
encerra o pipeline. Se outro delegado Use ou Run for adicionado após o delegado Run ,
ele não será chamado.

Prefira a sobrecarga app.Use que requer passar o


contexto para o próximo
O método de extensão app.Use sem alocação:

Requer passar o contexto para next .


Salva duas alocações internas por solicitação que são necessárias ao usar a outra
sobrecarga.

Saiba mais neste tópico do GitHub .

Ordem do middleware
O diagrama a seguir mostra o pipeline de processamento de solicitação completo para
aplicativos MVC do ASP.NET Core e Razor Pages. Você pode ver como, em um aplicativo
típico, os middlewares existentes são ordenados e onde middlewares personalizados
são adicionados. Você tem controle total sobre como reordenar os middlewares
existentes ou injetar novos middlewares personalizados conforme necessário para seus
cenários.

O middleware do Ponto de Extremidade no diagrama anterior executa o pipeline de


filtro para o tipo de aplicativo correspondente: MVC ou Razor Pages.

O middleware de Roteamento no diagrama anterior aparece seguindo Arquivos


Estáticos. Essa é a ordem que os modelos de projeto implementam chamando
explicitamente app.UseRouting. Se você não chamar app.UseRouting , o middleware de
Roteamento será executado no início do pipeline por padrão. Para obter mais
informações, consulte Roteamento.
A ordem em que os componentes do middleware são adicionados ao arquivo
Program.cs 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 seguinte código realçado em Program.cs adiciona componentes de middleware


relacionados à segurança na ordem recomendada típica:

C#

using IndividualAccountsExample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();

// Configure the HTTP request pipeline.


if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

No código anterior:

O middleware que não é adicionado ao criar um aplicativo Web com contas de


usuários individuais é comentado.
Nem todo middleware aparece nesta ordem exata, mas muitos aparecem. Por
exemplo:
UseCors , UseAuthentication e UseAuthorization devem aparecer na ordem

mostrada.
Atualmente, UseCors deve aparecer antes UseResponseCaching . Esse requisito é
explicado no problema do GitHub dotnet/aspnetcore #23218 .
UseRequestLocalization precisa aparecer antes de qualquer middleware que
possa verificar a cultura de solicitação (por exemplo,
app.UseMvcWithDefaultRoute() ).
Em alguns cenários, o middleware tem uma ordenação diferente. Por exemplo, o cache
e a ordenação de compactação são específicos do cenário e há várias ordenações
válidas. Por exemplo:

C#

app.UseResponseCaching();
app.UseResponseCompression();

Com o código anterior, o uso da CPU poderia ser reduzido armazenando em cache a
resposta compactada, mas você pode acabar armazenando em cache várias
representações de um recurso usando algoritmos de compactação diferentes, como
Gzip ou Brotli.

A seguinte ordenação combina arquivos estáticos para permitir o cache de arquivos


estáticos compactados:

C#

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

O seguinte código Program.cs adiciona componentes de middleware para cenários de


aplicativo comuns:

1. Exceção/tratamento de erro

Quando o aplicativo é executado no ambiente de desenvolvimento:


O middleware da página de exceção do desenvolvedor
(UseDeveloperExceptionPage) relata erros de runtime do aplicativo.
O middleware da página de erro do banco de dados
(UseDatabaseErrorPage) relata erros de runtime do banco de dados.
Quando o aplicativo é executado no ambiente de produção:
O middleware do manipulador de exceção (UseExceptionHandler) captura
exceções geradas nos middlewares a seguir.
O middleware do protocolo HTTP Strict Transport Security (HSTS)
(UseHsts) adiciona o cabeçalho Strict-Transport-Security .

2. O middleware de redirecionamento para HTTPS (UseHttpsRedirection) redireciona


as solicitações HTTP para HTTPS.
3. O middleware de arquivo estático (UseStaticFiles) retorna arquivos estáticos e
impede o processamento de novas solicitações.
4. O middleware da política de Cookie (UseCookiePolicy) adapta o aplicativo às
normas do RGPD (Regulamento Geral sobre a Proteção de Dados).
5. Middleware de Roteamento (UseRouting) para rotear solicitações.
6. O middleware de autenticação (UseAuthentication) tenta autenticar o usuário
antes de ele ter acesso aos recursos seguros.
7. O Middleware de Autorização (UseAuthorization) autoriza um usuário a acessar
recursos seguros.
8. O middleware de sessão (UseSession) estabelece e mantém o estado de sessão. Se
o aplicativo usa o estado de sessão, chame o middleware de sessão após o
middleware de política de Cookie, e antes do middleware do MVC.
9. Middleware de roteamento de ponto de extremidade (UseEndpoints com
MapRazorPages) para adicionar pontos de extremidade do Razor Pages ao
pipeline de solicitação.

C#

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

No código de exemplo anterior, cada método de extensão de middleware é exposto em


WebApplicationBuilder por meio do namespace Microsoft.AspNetCore.Builder.

UseExceptionHandler é o primeiro componente de middleware adicionado ao pipeline.


Portanto, o middleware de manipulador de exceção captura todas as exceções que
ocorrem em chamadas posteriores.

O Middleware de Arquivo Estático é chamado no início do pipeline para que possa


controlar as solicitações e causar o curto-circuito sem passar pelos componentes
restantes. O Middleware de Arquivo Estático não fornece nenhuma verificação de
autorização. Todos os arquivos fornecidos pelo Middleware de Arquivo Estático,
incluindo aqueles em wwwroot, estão disponíveis publicamente. Para conhecer uma
abordagem para proteger arquivos estáticos, veja Arquivos estáticos no ASP.NET Core.

Se a solicitação não for controlada pelo Middleware de Arquivo Estático, ela será
transmitida para o Middleware de Autenticação (UseAuthentication), que executa a
autenticação. A autenticação causa curto-circuito em solicitações não autenticadas.
Embora o Middleware de Autenticação autentique as solicitações, a autorização (e a
rejeição) ocorre somente depois que o MVC seleciona um Razor Page específica ou um
controlador MVC e uma ação.

O exemplo a seguir demonstra uma solicitação de middleware cujas solicitações de


arquivos estáticos são manipuladas pelo Middleware de Arquivo Estático antes do
Middleware de Compactação de Resposta. Arquivos estáticos não são compactados
com este pedido de middleware. As respostas do Razor Pages pode ser compactada.

C#

// Static files aren't compressed by Static File Middleware.


app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

Para obter informações sobre aplicativos de página única, consulte os guias dos
modelos de projeto React e Angular.

Ordem de UseCors e UseStaticFiles


A ordem de chamada de UseCors e UseStaticFiles depende do aplicativo. Para obter
mais informações, consulte Ordem de UseCors e UseStaticFiles

Ordem de Middleware de Cabeçalhos Encaminhados


Middlewares de Cabeçalhos Encaminhados devem ser executados antes de outros
middlewares. Essa ordenação garantirá que o middleware conte com informações de
cabeçalhos encaminhadas que podem consumir os valores de cabeçalho para
processamento. Para executar o Middleware de Cabeçalhos Encaminhados após o
diagnóstico e o middleware de tratamento de erros, consulte Ordem do Middleware de
Cabeçalhos Encaminhados.
Ramificar o pipeline do middleware
As extensões Map são usadas como uma convenção de ramificação do pipeline. Map
ramifica o pipeline de solicitação com base na correspondência do caminho da
solicitação em questão. Se o caminho da solicitação iniciar com o caminho especificado,
o branch será executado.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

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

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

app.Run(async context =>


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

app.Run();

static void HandleMapTest1(IApplicationBuilder app)


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

static void HandleMapTest2(IApplicationBuilder app)


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

A tabela a seguir mostra as solicitações e as respostas de http://localhost:1234


usando o código anterior.

Solicitação Resposta

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

localhost:1234/map1 Teste de Map 1

localhost:1234/map2 Teste de Map 2


Solicitação Resposta

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

Ao usar Map , os segmentos de caminho correspondentes são removidos de


HttpRequest.Path e anexados a HttpRequest.PathBase para cada solicitação.

Map é compatível com aninhamento, por exemplo:

C#

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


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

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

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>


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

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)


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

MapWhen ramifica o pipeline de solicitação com base no resultado do predicado


fornecido. Qualquer predicado do tipo Func<HttpContext, bool> pode ser usado para
mapear as solicitações para um novo branch do pipeline. No exemplo a seguir, um
predicado é usado para detectar a presença de uma variável de cadeia de caracteres de
consulta branch :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

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


HandleBranch);

app.Run(async context =>


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

app.Run();

static void HandleBranch(IApplicationBuilder app)


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

A tabela a seguir mostra as solicitações e as respostas de http://localhost:1234


usando o código anterior:

Solicitação Resposta

localhost:1234 Hello from non-Map delegate.

localhost:1234/?branch=main Branch used = main

UseWhen também ramifica o pipeline de solicitação com base no resultado do


predicado fornecido. Diferente do que acontece com MapWhen , esse branch será
retornado ao pipeline principal se ele não tiver um curto-circuito ou contiver um
middleware de terminal:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),


appBuilder => HandleBranchAndRejoin(appBuilder));
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)


{
var logger =
app.ApplicationServices.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>


{
var branchVer = context.Request.Query["branch"];
logger.LogInformation("Branch used = {branchVer}", branchVer);

// Do work that doesn't write to the Response.


await next();
// Do other work that doesn't write to the Response.
});
}

No exemplo anterior, uma resposta de Hello from non-Map delegate. é gravada para
todas as solicitações. Se a solicitação incluir uma variável de cadeia de caracteres de
consulta branch , seu valor será registrado antes que o pipeline principal seja retornado.

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
processamento da solicitação e sob quais condições o middleware podem encerrar o
processamento da solicitação. Quando um middleware causa um curto-circuito na
solicitação ao processar o pipeline e impede outros middleware downstream de
processar uma solicitação, ele é chamado de middleware terminal. Para saber mais sobre
curto-circuito, confira a seção Criar um pipeline de middleware com o
IApplicationBuilder.

Middleware Descrição Ordem

Autenticação Fornece suporte à Antes de HttpContext.User ser necessário.


autenticação. Terminal para retornos de chamada OAuth.

Autorização Fornece suporte à Imediatamente após o Middleware de


autorização. Autenticação.
Middleware Descrição Ordem

Política de Cookie Acompanha o Antes do middleware que emite cookies.


consentimento dos Exemplos: Autenticação, Sessão e MVC
usuários para o (TempData).
armazenamento de
informações pessoais
e impõe padrões
mínimos para campos
de cookie, tais como
secure e SameSite .

CORS Configura o Antes de componentes que usam o CORS.


Compartilhamento de Atualmente, UseCors deve ir antes de
Recursos entre UseResponseCaching devido a este bug .
Origens.

DeveloperExceptionPage Gera uma página com Antes dos componentes que geram erros. Os
informações de erro modelos de projeto registram
destinadas a uso automaticamente esse middleware como o
somente no ambiente primeiro middleware no pipeline quando o
de Desenvolvimento. ambiente é de Desenvolvimento.

Diagnóstico Vários middlewares Antes dos componentes que geram erros.


separados que Terminal para exceções ou para atender a
fornecem uma página página da Web padrão para novos
de exceção do aplicativos.
desenvolvedor,
tratamento de
exceções, páginas de
código de status e a
página da Web padrão
para novos aplicativos.

Cabeçalhos Encaminha cabeçalhos Antes dos componentes que consomem os


encaminhados como proxy para a campos atualizados. Exemplos: esquema,
solicitação atual. host, IP do cliente e método.

Verificações de Verifica a integridade Terminal, se uma solicitação corresponde a


integridade de um aplicativo um ponto de extremidade da verificação de
ASP.NET Core e suas integridade.
dependências, como a
verificação da
disponibilidade do
banco de dados.
Middleware Descrição Ordem

Propagação de Propaga cabeçalhos


cabeçalho HTTP da solicitação de
entrada para
solicitações de cliente
HTTP de saída.

Log HTTP Registra solicitações e No início do pipeline do middleware.


respostas HTTP.

Substituição do Método Permite que uma Antes dos componentes que consomem o
HTTP solicitação de entrada método atualizado.
POST substitua o
método.

Redirecionamento de Redirecione todas as Antes dos componentes que consomem a


HTTPS solicitações HTTP para URL.
HTTPS.

Segurança de Transporte Middleware de Antes das respostas serem enviadas e depois


Estrita de HTTP (HSTS) aprimoramento de dos componentes que modificam
segurança que solicitações. Exemplos: Cabeçalhos
adiciona um encaminhados, regravação de URL.
cabeçalho de resposta
especial.

MVC Processa as Terminal, se uma solicitação corresponder a


solicitações com uma rota.
MVC/Razor Pages.

OWIN Interoperabilidade Terminal, se o middleware OWIN processa


com aplicativos totalmente a solicitação.
baseados em OWIN,
em servidores e em
middleware.

Cache de saída Fornece suporte para Antes dos componentes que exigem
respostas de cache armazenamento em cache. UseRouting deve
com base na vir antes de UseOutputCaching . UseCORS deve
configuração. vir antes de UseOutputCaching .
Middleware Descrição Ordem

Cache de resposta Fornece suporte para Antes dos componentes que exigem
as respostas em cache. armazenamento em cache. UseCORS deve vir
Isso requer a antes de UseResponseCaching . Normalmente,
participação do cliente não é benéfico para aplicativos de interface
para funcionar. Use o do usuário, como Razor o Pages, porque os
cache de saída para o navegadores geralmente definem
controle completo do cabeçalhos de solicitação que impedem o
servidor. cache. O cache de saída beneficia aplicativos
de interface do usuário.

Solicitação de Dá suporte a Antes dos componentes que leem o corpo


descompactação solicitações de da solicitação.
descompactação.

Compactação de Fornece suporte para Antes dos componentes que exigem


resposta a compactação de compactação.
respostas.

Localização de Fornece suporte à Antes dos componentes de localização


Solicitação localização. importantes. Deve aparecer após o
Middleware de Roteamento ao usar
RouteDataRequestCultureProvider.

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


de extremidade rotas de solicitação.

SPA Manipula todas as No final da cadeia, para que outro


solicitações desse middleware que serve arquivos estáticos,
ponto na cadeia de ações de MVC etc., tenha precedência.
middleware
retornando a página
padrão do SPA
(Aplicativo de Página
Única)

Sessão Fornece suporte para Antes de componentes que exigem a sessão.


gerenciar sessões de
usuário.

Arquivos estáticos Fornece suporte para Terminal, se uma solicitação corresponde a


servir arquivos um arquivo.
estáticos e pesquisa
no diretório.

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

W3CLogging Gera logs de acesso No início do pipeline do middleware.


do servidor no
Formato de arquivo
de log estendido
W3C .

WebSockets Habilita o protocolo Antes dos componentes que são necessários


WebSockets. para aceitar solicitações de WebSocket.

Recursos adicionais
As opções de tempo de vida e registro contêm um exemplo completo de
middleware com serviços de tempo de vida com escopo, transitórios e singleton.
Escrever middleware do ASP.NET Core personalizado
Testar middleware do ASP.NET Core
Configurar gRPC-Web no ASP.NET Core
Migrar manipuladores e módulos 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 genérico do .NET no ASP.NET Core
Artigo • 10/01/2023 • 38 minutos para o fim da leitura

Este artigo fornece informações sobre como usar o Host Genérico do .NET no ASP.NET
Core.

Os modelos de ASP.NET Core criam um WebApplicationBuilder e WebApplication, que


fornecem uma maneira simplificada de configurar e executar aplicativos Web sem uma
Startup classe. Para obter mais informações sobre WebApplicationBuilder e

WebApplication , consulte Migrar de ASP.NET Core 5.0 para 6.0.

Para obter informações sobre como usar o Host Genérico do .NET em aplicativos de
console, consulte Host Genérico do .NET.

Definição do host
Um host é um objeto que encapsula os recursos de um aplicativo, tais como:

DI (injeção de dependência)
Log
Configuração
Implementações de IHostedService

Quando um host é iniciado, ele chama IHostedService.StartAsync em cada


implementação de IHostedService registrada na coleção de serviços hospedados do
contêiner de serviço. Em um aplicativo Web, uma das implementações de
IHostedService é um serviço Web que inicia uma implementação do servidor HTTP.

Incluir todos os recursos interdependentes do aplicativo em um objeto permite o


controle sobre a inicialização do aplicativo e o desligamento normal.

Configurar um host
O host normalmente é configurado, compilado e executado por código no Program.cs .
O código a seguir cria um host com uma implementação IHostedService adicionada ao
contêiner de DI:

C#

await Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<SampleHostedService>();
})
.Build()
.RunAsync();

Para uma carga de trabalho HTTP, chame ConfigureWebHostDefaults após


CreateDefaultBuilder:

C#

await Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build()
.RunAsync();

Configurações do construtor padrão


O método CreateDefaultBuilder:

Define a raiz do conteúdo como o caminho retornado por GetCurrentDirectory.


Carrega a configuração do host de:
Variáveis de ambiente prefixadas com DOTNET_ .
Argumentos de linha de comando.
Carrega a configuração do aplicativo de:
appsettings.json .

appsettings.{Environment}.json .
Segredos do usuário quando o aplicativo é executado no ambiente
Development .
Variáveis de ambiente.
Argumentos de linha de comando.
Adiciona os seguintes provedores de log :
Console
Depurar
EventSource
EventLog (somente quando em execução no Windows)
Habilita a validação de escopo e a validação de dependência quando o ambiente é
o de Desenvolvimento.

O método ConfigureWebHostDefaults:
Carrega a configuração do host de variáveis de ambiente prefixadas com
ASPNETCORE_ .
Define Kestrel o servidor como o servidor Web e o configura usando os
provedores de configuração de hospedagem do aplicativo. Para obter as Kestrel
opções padrão do servidor, consulte Configurar opções para o servidor Web
ASP.NET CoreKestrel.
Adiciona middleware de filtragem de Host.
Adiciona o middleware Cabeçalhos Encaminhados se
ASPNETCORE_FORWARDEDHEADERS_ENABLED for igual true a .
Habilita a integração de IIS. Para obter as opções padrão do IIS, consulte Host
ASP.NET Core no Windows com o IIS.

As seções Configurações para todos os tipos de aplicativo e Configurações para


aplicativos Web neste artigo mostram como substituir as configurações do construtor
padrão.

Serviços fornecidos pela estrutura


Os seguintes serviços são registrados automaticamente:

IHostApplicationLifetime
IHostLifetime
IHostEnvironment/IWebHostEnvironment

Para obter mais informações sobre serviços fornecidos pela estrutura, consulte Injeção
de dependência em ASP.NET Core.

IHostApplicationLifetime
Injete o serviço IHostApplicationLifetime (anteriormente conhecido como
IApplicationLifetime ) em qualquer classe para lidar com tarefas de pós-inicialização e

de desligamento normal. Três propriedades na interface são tokens de cancelamento


usados para registrar métodos de manipulador de eventos de inicialização e
desligamento do aplicativo. A interface também inclui um StopApplication método ,
que permite que os aplicativos solicitem um desligamento normal.

Ao executar um desligamento normal, o host:

Dispara os ApplicationStopping manipuladores de eventos, o que permite que o


aplicativo execute a lógica antes do início do processo de desligamento.
Interrompe o servidor, que desabilita novas conexões. O servidor aguarda a
conclusão das solicitações em conexões existentes, desde que o tempo limite de
desligamento permita. O servidor envia o cabeçalho de fechamento de conexão
para solicitações adicionais em conexões existentes.
Dispara os ApplicationStopped manipuladores de eventos, o que permite que o
aplicativo execute a lógica após o desligamento do aplicativo.

O exemplo a seguir é uma implementação IHostedService que registra manipuladores


IHostApplicationLifetime de eventos:

C#

public class HostApplicationLifetimeEventsHostedService : IHostedService


{
private readonly IHostApplicationLifetime _hostApplicationLifetime;

public HostApplicationLifetimeEventsHostedService(
IHostApplicationLifetime hostApplicationLifetime)
=> _hostApplicationLifetime = hostApplicationLifetime;

public Task StartAsync(CancellationToken cancellationToken)


{
_hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
_hostApplicationLifetime.ApplicationStopping.Register(OnStopping);
_hostApplicationLifetime.ApplicationStopped.Register(OnStopped);

return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)


=> Task.CompletedTask;

private void OnStarted()


{
// ...
}

private void OnStopping()


{
// ...
}

private void OnStopped()


{
// ...
}
}

IHostLifetime
A implementação IHostLifetime controla quando o host é iniciado e quando ele é
interrompido. A última implementação registrada é usada.

Microsoft.Extensions.Hosting.Internal.ConsoleLifetime é a implementação

IHostLifetime padrão. ConsoleLifetime :

Escuta Ctrl + C /SIGINT (Windows), ⌘ + C (macOS) ou SIGTERM e chama


StopApplication para iniciar o processo de desligamento.
Desbloqueia extensões como RunAsync e WaitForShutdownAsync.

IHostEnvironment
Injete o serviço IHostEnvironment em uma classe para obter informações sobre as
seguintes configurações:

ApplicationName
EnvironmentName
ContentRootPath

Os aplicativos Web implementam a IWebHostEnvironment interface , que herda


IHostEnvironment e adiciona o WebRootPath.

Configuração do host
A configuração do host é usada para as propriedades da implementação
IHostEnvironment.

A configuração do host está disponível de dentro ConfigureAppConfigurationdo


HostBuilderContext.Configuration . Após ConfigureAppConfiguration ,
HostBuilderContext.Configuration é substituído com a configuração do aplicativo.

Para adicionar a configuração do host, chame ConfigureHostConfiguration em


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

O provedor de variável de ambiente com argumentos de prefixo DOTNET_ e linha de


comando são incluídos pelo CreateDefaultBuilder . Para aplicativos Web, o provedor de
variáveis de ambiente com o prefixo ASPNETCORE_ é adicionado. O prefixo é removido
quando as variáveis de ambiente são lidas. Por exemplo, o valor da variável de ambiente
de ASPNETCORE_ENVIRONMENT torna-se o valor de configuração de host para a chave
environment .

O exemplo a seguir cria a configuração de host:

C#

Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(hostConfig =>
{
hostConfig.SetBasePath(Directory.GetCurrentDirectory());
hostConfig.AddJsonFile("hostsettings.json", optional: true);
hostConfig.AddEnvironmentVariables(prefix: "PREFIX_");
hostConfig.AddCommandLine(args);
});

Configuração do aplicativo
A configuração de aplicativo é criada chamando ConfigureAppConfiguration em
IHostBuilder . 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 por ConfigureAppConfiguration está disponível em


HostBuilderContext.Configuration para operações subsequentes e como um serviço da
DI. A configuração do host também é adicionada à configuração do aplicativo.

Para obter mais informações, consulte Configuração no ASP.NET Core.

Configurações para todos os tipos de aplicativo


Esta seção lista as configurações de host que se aplicam a cargas de trabalho HTTP e
àquelas não HTTP. Por padrão, as variáveis de ambiente usadas para definir essas
configurações podem ter um DOTNET_ prefixo ou ASPNETCORE_ , que aparece na lista de
configurações a seguir como o {PREFIX_} espaço reservado. Para obter mais
informações, consulte a seção Configurações do construtor padrão e Configuração:
variáveis de ambiente.

ApplicationName
A IHostEnvironment.ApplicationName propriedade é definida a partir da configuração
do host durante a construção do host.
Chave: applicationName
Tipo: string
Padrão: o nome do assembly que contém o ponto de entrada do aplicativo.
Variável de ambiente: {PREFIX_}APPLICATIONNAME

Para definir esse valor, use a variável de ambiente.

ContentRoot
A IHostEnvironment.ContentRootPath propriedade determina onde o host começa a
pesquisar arquivos de conteúdo. Se o caminho não existir, o host não será iniciado.

Chave: contentRoot
Tipo: string
Padrão: a pasta na qual o assembly do aplicativo reside.
Variável de ambiente: {PREFIX_}CONTENTROOT

Para definir esse valor, use a variável de ambiente ou a chamada UseContentRoot em


IHostBuilder :

C#

Host.CreateDefaultBuilder(args)
.UseContentRoot("/path/to/content/root")
// ...

Para obter mais informações, consulte:

Conceitos básicos: raiz de conteúdo


WebRoot

EnvironmentName
A IHostEnvironment.EnvironmentName propriedade pode ser definida como qualquer
valor. Os valores definidos pela estrutura incluem Development , Staging e Production .
Os valores não diferenciam maiúsculas de minúsculas.

Chave: environment
Tipo: string
Padrão: Production
Variável de ambiente: {PREFIX_}ENVIRONMENT
Para definir esse valor, use a variável de ambiente ou a chamada UseEnvironment em
IHostBuilder :

C#

Host.CreateDefaultBuilder(args)
.UseEnvironment("Development")
// ...

ShutdownTimeout
O HostOptions.ShutdownTimeout define o tempo limite para StopAsync. O valor padrão
é cinco segundos. Durante o período de tempo limite, o host:

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

Chave: shutdownTimeoutSeconds
Tipo: int
Padrão: 5 segundos
Variável de ambiente: {PREFIX_}SHUTDOWNTIMEOUTSECONDS

Para definir esse valor, use a variável de ambiente ou configure HostOptions . O exemplo
a seguir define o tempo limite para 20 segundos:

C#

Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.Configure<HostOptions>(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds(20);
});
});
Desabilitar o recarregamento da configuração do
aplicativo na alteração
Por padrão, appsettings.json e appsettings.{Environment}.json são recarregados
quando o arquivo é alterado. Para desabilitar esse comportamento de recarregamento
no ASP.NET Core 5.0 ou posterior, defina a hostBuilder:reloadConfigOnChange chave
como false .

Chave: hostBuilder:reloadConfigOnChange
Tipo: bool ( true ou false )
Padrão: true
Argumento de linha de comando: hostBuilder:reloadConfigOnChange
Variável de ambiente: {PREFIX_}hostBuilder:reloadConfigOnChange

2 Aviso

O separador de dois-pontos ( : ) não funciona com chaves hierárquicas de variável


de ambiente em todas as plataformas. Para obter mais informações, confira
Variáveis de ambiente.

Configurações para aplicativos Web


Algumas configurações de host se aplicam somente a cargas de trabalho HTTP. Por
padrão, as variáveis de ambiente usadas para definir essas configurações podem ter um
DOTNET_ prefixo ou ASPNETCORE_ , que aparece na lista de configurações a seguir como o

{PREFIX_} espaço reservado.

Métodos de extensão em IWebHostBuilder estão disponíveis para essas configurações.


Exemplos de código que mostram como chamar os métodos de extensão pressupõem
que webBuilder é uma instância de IWebHostBuilder , conforme mostrado no exemplo a
seguir:

C#

Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// ...
});
CaptureStartupErrors
Quando false , erros durante a inicialização resultam no encerramento do host. Quando
true , o host captura exceções durante a inicialização e tenta iniciar o servidor.

Chave: captureStartupErrors
Tipo: bool ( true / 1 ou false / 0 )
Padrão: o padrão é a false menos que o aplicativo seja executado com Kestrel o IIS por
trás, onde o padrão é true .
Variável de ambiente: {PREFIX_}CAPTURESTARTUPERRORS

Para definir esse valor, use a configuração ou a chamada CaptureStartupErrors :

C#

webBuilder.CaptureStartupErrors(true);

DetailedErrors
Quando habilitado (ou quando o ambiente é Development ), o aplicativo captura erros
detalhados.

Chave: detailedErrors
Tipo: bool ( true / 1 ou false / 0 )
Padrão: false
Variável de ambiente: {PREFIX_}DETAILEDERRORS

Para definir esse valor, use a configuração ou a chamada UseSetting :

C#

webBuilder.UseSetting(WebHostDefaults.DetailedErrorsKey, "true");

HostingStartupAssemblies
Uma cadeia de caracteres delimitada por ponto e vírgula de assemblies de inicialização
de hospedagem para carregamento na inicialização. Embora o valor padrão da
configuração seja uma cadeia de caracteres vazia, os assemblies de inicialização de
hospedagem sempre incluem o assembly do aplicativo. Quando assemblies de
inicialização de hospedagem são fornecidos, eles são adicionados ao assembly do
aplicativo para carregamento quando o aplicativo compilar seus serviços comuns
durante a inicialização.

Chave: hostingStartupAssemblies
Tipo: string
Padrão: cadeia de caracteres vazia
Variável de ambiente: {PREFIX_}HOSTINGSTARTUPASSEMBLIES

Para definir esse valor, use a configuração ou a chamada UseSetting :

C#

webBuilder.UseSetting(
WebHostDefaults.HostingStartupAssembliesKey, "assembly1;assembly2");

HostingStartupExcludeAssemblies
Uma cadeia de caracteres delimitada por ponto e vírgula de assemblies de inicialização
de hospedagem para exclusão na inicialização.

Chave: hostingStartupExcludeAssemblies
Tipo: string
Padrão: cadeia de caracteres vazia
Variável de ambiente: {PREFIX_}HOSTINGSTARTUPEXCLUDEASSEMBLIES

Para definir esse valor, use a configuração ou a chamada UseSetting :

C#

webBuilder.UseSetting(
WebHostDefaults.HostingStartupExcludeAssembliesKey,
"assembly1;assembly2");

HTTPS_Port
A porta de redirecionamento HTTPS. Uso em aplicação de HTTPS.

Chave: https_port
Tipo: string
Padrão: um valor padrão não é definido.
Variável de ambiente: {PREFIX_}HTTPS_PORT

Para definir esse valor, use a configuração ou a chamada UseSetting :


C#

webBuilder.UseSetting("https_port", "8080");

PreferHostingUrls
Indica se o host deve escutar nas URLs configuradas com o IWebHostBuilder em vez
dessas URLs configuradas com a IServer implementação.

Chave: preferHostingUrls
Tipo: bool ( true / 1 ou false / 0 )
Padrão: true
Variável de ambiente: {PREFIX_}PREFERHOSTINGURLS

Para definir esse valor, use a variável de ambiente ou a chamada PreferHostingUrls :

C#

webBuilder.PreferHostingUrls(true);

PreventHostingStartup
Impede o carregamento automático de assemblies de inicialização de hospedagem,
incluindo assemblies de inicialização de hospedagem configurados pelo assembly do
aplicativo. Para obter mais informações, confira Usar assemblies de inicialização de
hospedagem no ASP.NET Core.

Chave: preventHostingStartup
Tipo: bool ( true / 1 ou false / 0 )
Padrão: false
Variável de ambiente: {PREFIX_}PREVENTHOSTINGSTARTUP

Para definir esse valor, use a variável de ambiente ou a chamada UseSetting :

C#

webBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true");

StartupAssembly
O assembly no qual pesquisar pela classe Startup .
Chave: startupAssembly
Tipo: string
Padrão: o assembly do aplicativo
Variável de ambiente: {PREFIX_}STARTUPASSEMBLY

Para definir esse valor, use a variável de ambiente ou a chamada UseStartup .


UseStartup pode usar um nome de assembly ( string ) ou um tipo ( TStartup ). Se vários

métodos UseStartup forem chamados, o último terá precedência.

C#

webBuilder.UseStartup("StartupAssemblyName");

C#

webBuilder.UseStartup<Startup>();

SuppressStatusMessages
Quando habilitado, suprime a hospedagem de mensagens de status de inicialização.

Chave: suppressStatusMessages
Tipo: bool ( true / 1 ou false / 0 )
Padrão: false
Variável de ambiente: {PREFIX_}SUPPRESSSTATUSMESSAGES

Para definir esse valor, use a configuração ou a chamada UseSetting :

C#

webBuilder.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "true");

URLs
Uma lista delimitada por ponto-e-vírgula de endereços IP ou endereços de host com
portas e protocolos que o servidor deve escutar para solicitações. 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.
Chave: urls
Tipo: string
Padrão: http://localhost:5000 e https://localhost:5001
Variável de ambiente: {PREFIX_}URLS

Para definir esse valor, use a variável de ambiente ou a chamada UseUrls :

C#

webBuilder.UseUrls("http://*:5000;http://localhost:5001;https://hostname:500
2");

Kestrel tem sua própria API de configuração de ponto de extremidade. Para obter mais
informações, consulte Configurar pontos de extremidade para o servidor Web ASP.NET
CoreKestrel.

WebRoot
A propriedade IWebHostEnvironment.WebRootPath determina o caminho relativo para
os ativos estáticos do aplicativo. Se o caminho não existir, um provedor de arquivo não
operacional será usado.

Chave: webroot
Tipo: string
Padrão: o padrão é wwwroot . O caminho para {raiz de conteúdo}/wwwroot deve existir.
Variável de ambiente: {PREFIX_}WEBROOT

Para definir esse valor, use a variável de ambiente ou a chamada UseWebRoot em


IWebHostBuilder :

C#

webBuilder.UseWebRoot("public");

Para obter mais informações, consulte:

Conceitos básicos: raiz da Web


ContentRoot

Gerenciar o tempo de vida do host


Chame métodos na implementação de IHost criada para iniciar e parar o aplicativo.
Esses métodos afetam todas as IHostedService implementações registradas no
contêiner de serviço.

Executar
Run executa o aplicativo e bloqueia o thread de chamada até que o host seja desligado.

RunAsync
RunAsync executa o aplicativo e retorna um Task, que é concluído quando o token de
cancelamento ou o desligamento é disparado.

RunConsoleAsync
RunConsoleAsync habilita o suporte ao console, cria e inicia o host e aguarda ctrl + C

/SIGINT (Windows), ⌘ + C (macOS) ou SIGTERM para desligar.

Iniciar
Start inicia o host de forma síncrona.

StartAsync
StartAsync inicia o host e retorna um Task, que é concluído quando o token de
cancelamento ou o desligamento é disparado.

WaitForStartAsync é chamado no início de StartAsync , que aguarda até que ele seja
concluído antes de continuar. Esse método pode ser usado para atrasar a inicialização
até ser sinalizado por um evento externo.

StopAsync
StopAsync tenta parar o host dentro do tempo limite fornecido.

WaitForShutdown
WaitForShutdown bloqueia o thread de chamada até que o desligamento seja
disparado pelo IHostLifetime, como por meio de Ctrl + C /SIGINT (Windows), ⌘ + C

(macOS) ou SIGTERM.
WaitForShutdownAsync
WaitForShutdownAsync retorna um Task que é concluído quando o desligamento é
disparado por meio do token fornecido e chama StopAsync.

Recursos adicionais
Tarefas em segundo plano com serviços hospedados no ASP.NET Core
Link do GitHub para a origem do Host Genérico

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .
Host da Web do ASP.NET Core
Artigo • 10/01/2023 • 50 minutos para o fim da leitura

Aplicativos ASP.NET Core configuram e inicializam um host. O host é responsável pelo


gerenciamento de tempo de vida e pela inicialização do aplicativo. No mínimo, o host
configura um servidor e um pipeline de processamento de solicitações. O host também
pode configurar registro em log, a injeção de dependência e a configuração.

Este artigo aborda o host da Web, que permanece disponível somente para
compatibilidade com versões anteriores. Os modelos de ASP.NET Core criam um
WebApplicationBuilder e WebApplication, que é recomendado para aplicativos Web.
Para obter mais informações sobre WebApplicationBuilder e WebApplication , consulte
Migrar de ASP.NET Core 5.0 para 6.0

Configurar um host
Crie um host usando uma instância do IWebHostBuilder. Normalmente, isso é
executado no ponto de entrada do aplicativo, o Main método em Program.cs . Um
aplicativo típico chama CreateDefaultBuilder para iniciar a configuração de um host:

C#

public class Program


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

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>


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

O código que chama CreateDefaultBuilder está em um método chamado


CreateWebHostBuilder , que o separa do código em Main , que chama Run no objeto de
construtor. Essa separação será necessária se você usar ferramentas do Entity
Framework Core. As ferramentas esperam encontrar um método CreateWebHostBuilder
que elas possam chamar em tempo de design para configurar o host sem executar o
aplicativo. Uma alternativa é implementar IDesignTimeDbContextFactory . Para obter mais
informações, confira Criação de DbContext no tempo de design.
CreateDefaultBuilder realiza as seguintes tarefas:

Kestrel Configura o servidor como o servidor Web usando os provedores de


configuração de hospedagem do aplicativo. Para obter as Kestrel opções padrão
do servidor, consulte Configurar opções para o servidor Web ASP.NET CoreKestrel.
Define a raiz do conteúdo como o caminho retornado por
Directory.GetCurrentDirectory.
Carrega a configuração do host de:
Variáveis de ambiente prefixadas com ASPNETCORE_ (por exemplo,
ASPNETCORE_ENVIRONMENT ).
Argumentos de linha de comando.
Carrega a configuração do aplicativo na seguinte ordem de:
appsettings.json .
appsettings.{Environment}.json .

Segredos do usuário quando o aplicativo é executado no ambiente Development


usando o assembly de entrada.
Variáveis de ambiente.
Argumentos de linha de comando.
Configura o registro em log para a saída do console e de depuração. O registro em
log inclui regras de filtragem de log especificadas em uma seção de configuração
de registro em log de um appsettings.json arquivo ou appsettings.
{Environment}.json .
Ao executar o IIS por trás do módulo ASP.NET Core, CreateDefaultBuilder habilita
a Integração do IIS, que configura o endereço base e a porta do aplicativo. A
Integração do IIS também configura o aplicativo para capturar erros de
inicialização. Para obter as opções padrão do IIS, consulte Host ASP.NET Core no
Windows com IIS.
Define ServiceProviderOptions.ValidateScopes como true se o ambiente do
aplicativo é Desenvolvimento. Para obter mais informações, confira Validação de
escopo.

A configuração definida por CreateDefaultBuilder pode ser substituída e aumentada


por ConfigureAppConfiguration, ConfigureLogginge outros métodos e métodos de
extensão de IWebHostBuilder. Veja a seguir alguns exemplos:

ConfigureAppConfiguration é usado para especificar adicionais IConfiguration


para o aplicativo. A chamada a seguir ConfigureAppConfiguration adiciona um
delegado para incluir a configuração do aplicativo no appsettings.xml arquivo.
ConfigureAppConfiguration pode ser chamado várias vezes. Observe que essa
configuração não se aplica ao host (por exemplo, URLs de servidor ou de
ambiente). Consulte a seção Valores de configuração de Host.

C#

WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddXmlFile("appsettings.xml", optional: true,
reloadOnChange: true);
})
...

A chamada a seguir ConfigureLogging adiciona um delegado para configurar o


nível mínimo de log (SetMinimumLevel) para LogLevel.Warning. Essa configuração
substitui as configurações em appsettings.Development.json ( LogLevel.Debug ) e
appsettings.Production.json ( LogLevel.Error ) definidas por
CreateDefaultBuilder . ConfigureLogging pode ser chamado várias vezes.

C#

WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Warning);
})
...

A chamada a seguir para ConfigureKestrel substitui o padrão


Limits.MaxRequestBodySize de 30.000.000 bytes estabelecidos quando Kestrel foi
configurado por CreateDefaultBuilder :

C#

WebHost.CreateDefaultBuilder(args)
.ConfigureKestrel((context, 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 aplicativos, consulte Configuração
no ASP.NET Core.

7 Observação

Como alternativa ao uso do método estático CreateDefaultBuilder , criar um host


do WebHostBuilder é uma abordagem com suporte com ASP.NET Core 2.x.

Ao configurar um host, Configure os métodos e ConfigureServices podem ser


fornecidos. Se uma classe Startup for especificada, ela deverá definir um método
Configure . Saiba mais em Inicialização de aplicativos no ASP.NET Core. Diversas

chamadas para ConfigureServices são acrescentadas umas às outras. Diversas


chamadas para Configure ou UseStartup no WebHostBuilder substituem configurações
anteriores.

Valores de configuração do host


WebHostBuilder depende das seguintes abordagens para definir os valores de
configuração do host:

Configuração do construtor do host, que inclui variáveis de ambiente com o


formato ASPNETCORE_{configurationKey} . Por exemplo, ASPNETCORE_ENVIRONMENT .
Extensões como UseContentRoot e UseConfiguration (consulte a seção Substituir
configuração ).
UseSetting e a chave associada. Ao definir um valor com UseSetting , o valor é
definido como uma cadeia de caracteres, independentemente do tipo.

O host usa a opção que define um valor por último. Para obter mais informações, veja
Substituir configuração na próxima seção.

Chave do Aplicativo (Nome)


A IWebHostEnvironment.ApplicationName propriedade é definida automaticamente
quando UseStartup ou Configure é chamada durante a construção do host. O valor é
definido para o nome do assembly que contém o ponto de entrada do aplicativo. Para
definir o valor explicitamente, use o WebHostDefaults.ApplicationKey:

Chave: applicationName
Tipo: cadeia de caracteres
Padrão: o nome do assembly que contém o ponto de entrada do aplicativo.
Defina usando: UseSetting
Variável de ambiente: ASPNETCORE_APPLICATIONNAME

C#

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.ApplicationKey, "CustomApplicationName")

Capturar erros de inicialização


Esta configuração controla a captura de erros de inicialização.

Chave: captureStartupErrors
Tipo: bool ( true ou 1 )
Padrão: o padrão é a false menos que o aplicativo seja executado com Kestrel o IIS por
trás, onde o padrão é true .
Defina usando: CaptureStartupErrors
Variável de ambiente: ASPNETCORE_CAPTURESTARTUPERRORS

Quando false , erros durante a inicialização resultam no encerramento do host. Quando


true , o host captura exceções durante a inicialização e tenta iniciar o servidor.

C#

WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(true)

Raiz do conteúdo
Essa configuração determina onde ASP.NET Core começa a pesquisar arquivos de
conteúdo.

Chave: contentRoot
Tipo: cadeia de caracteres
Padrão: o padrão é a pasta em que o assembly do aplicativo reside.
Defina usando: UseContentRoot
Variável de ambiente: ASPNETCORE_CONTENTROOT

A raiz de conteúdo também é usada como o caminho base para a raiz da Web. Se o
caminho raiz do conteúdo não existir, o host falhará ao iniciar.

C#
WebHost.CreateDefaultBuilder(args)
.UseContentRoot("c:\\<content-root>")

Para obter mais informações, consulte:

Conceitos básicos: raiz de conteúdo


Raiz da Web

Erros detalhados
Determina se erros detalhados devem ser capturados.

Chave: detailedErrors
Tipo: bool ( true ou 1 )
Padrão: false
Defina usando: UseSetting
Variável de ambiente: ASPNETCORE_DETAILEDERRORS

Quando habilitado (ou quando o Ambiente é definido como Development ), o aplicativo


captura exceções detalhadas.

C#

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")

Ambiente
Define o ambiente do aplicativo.

Chave: ambiente
Tipo: cadeia de caracteres
Padrão: Production
Defina 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 , as variáveis de ambiente podem ser
definidas no launchSettings.json arquivo. Para obter mais informações, confira Usar
vários ambientes no ASP.NET Core.
C#

WebHost.CreateDefaultBuilder(args)
.UseEnvironment(EnvironmentName.Development)

Hospedando assemblies de inicialização


Define os assemblies de inicialização de hospedagem do aplicativo.

Chave: hostingStartupAssemblies
Tipo: cadeia de caracteres
Padrão: cadeia de caracteres vazia
Defina usando: UseSetting
Variável de ambiente: ASPNETCORE_HOSTINGSTARTUPASSEMBLIES

Uma cadeia de caracteres delimitada por ponto e vírgula de assemblies de inicialização


de hospedagem para carregamento na inicialização.

Embora o valor padrão da configuração seja uma cadeia de caracteres vazia, os


assemblies de inicialização de hospedagem sempre incluem o assembly do aplicativo.
Quando assemblies de inicialização de hospedagem são fornecidos, eles são
adicionados ao assembly do aplicativo para carregamento quando o aplicativo compilar
seus serviços comuns durante a inicialização.

C#

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 é definido.
Defina usando: UseSetting
Variável de ambiente: ASPNETCORE_HTTPS_PORT

C#
WebHost.CreateDefaultBuilder(args)
.UseSetting("https_port", "8080")

Hospedando assemblies de exclusão de inicialização


Uma cadeia de caracteres delimitada por ponto e vírgula de assemblies de inicialização
de hospedagem para exclusão na inicialização.

Chave: hostingStartupExcludeAssemblies
Tipo: cadeia de caracteres
Padrão: cadeia de caracteres vazia
Defina usando: UseSetting
Variável de ambiente: ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES

C#

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey,
"assembly1;assembly2")

Preferir URLs de hospedagem


Indica se o host deve escutar as URLs configuradas com o WebHostBuilder em vez
daquelas configuradas com a implementação IServer .

Chave: preferHostingUrls
Tipo: bool ( true ou 1 )
Padrão: true
Defina usando: PreferHostingUrls
Variável de ambiente: ASPNETCORE_PREFERHOSTINGURLS

C#

WebHost.CreateDefaultBuilder(args)
.PreferHostingUrls(false)

Impedir inicialização de hospedagem


Impede o carregamento automático de assemblies de inicialização de hospedagem,
incluindo assemblies de inicialização de hospedagem configurados pelo assembly do
aplicativo. Para obter mais informações, confira Usar assemblies de inicialização de
hospedagem no ASP.NET Core.

Chave: preventHostingStartup
Tipo: bool ( true ou 1 )
Padrão: false
Defina usando: UseSetting
Variável de ambiente: ASPNETCORE_PREVENTHOSTINGSTARTUP

C#

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: cadeia de caracteres
Padrão: http://localhost:5000
Defina 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.

C#

WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")

Kestrel tem sua própria API de configuração de ponto de extremidade. Para obter mais
informações, consulte Configurar pontos de extremidade para o servidor Web ASP.NET
CoreKestrel.

Tempo limite de desligamento


Especifica o tempo de espera para o desligamento do host da Web.

Chave: shutdownTimeoutSeconds
Tipo: int
Padrão: 5
Defina usando: UseShutdownTimeout
Variável de ambiente: ASPNETCORE_SHUTDOWNTIMEOUTSECONDS

Embora a chave aceite um int com UseSetting (por exemplo,


.UseSetting(WebHostDefaults.ShutdownTimeoutKey, "10") ), o UseShutdownTimeout

método de extensão usa um TimeSpan.

Durante o período de tempo limite, a hospedagem:

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

C#

WebHost.CreateDefaultBuilder(args)
.UseShutdownTimeout(TimeSpan.FromSeconds(10))

Assembly de inicialização
Determina o assembly para pesquisar pela classe Startup .

Chave: startupAssembly
Tipo: cadeia de caracteres
Padrão: o assembly do aplicativo
Defina 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.

C#
WebHost.CreateDefaultBuilder(args)
.UseStartup("StartupAssemblyName")

C#

WebHost.CreateDefaultBuilder(args)
.UseStartup<TStartup>()

Raiz da Web
Define o caminho relativo para os ativos estáticos do aplicativo.

Chave: webroot
Tipo: cadeia de caracteres
Padrão: o padrão é wwwroot . O caminho para {raiz de conteúdo}/wwwroot deve existir. Se
o caminho não existir, um provedor de arquivo não operacional será usado.
Defina usando: UseWebRoot
Variável de ambiente: ASPNETCORE_WEBROOT

C#

WebHost.CreateDefaultBuilder(args)
.UseWebRoot("public")

Para obter mais informações, consulte:

Conceitos básicos: raiz da Web


Raiz do conteúdo

Substituir configuração
Use Configuração para configurar o host da Web. No exemplo a seguir, a configuração
do host é opcionalmente especificada em um hostsettings.json arquivo. Qualquer
configuração carregada do hostsettings.json arquivo pode ser substituída por
argumentos de linha de comando. A configuração interna (no config ) é usada para
configurar o host com UseConfiguration. IWebHostBuilder A configuração é adicionada
à configuração do aplicativo, mas o inverso não é
verdadeiro, ConfigureAppConfiguration não afeta a IWebHostBuilder configuração.

Substituindo a configuração fornecida por UseUrls com hostsettings.json


configuração primeiro, configuração de argumento de linha de comando segundo:
C#

public class Program


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

public static IWebHostBuilder CreateWebHostBuilder(string[] args)


{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("hostsettings.json", optional: true)
.AddCommandLine(args)
.Build();

return WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000")
.UseConfiguration(config)
.Configure(app =>
{
app.Run(context =>
context.Response.WriteAsync("Hello, World!"));
});
}
}

hostsettings.json :

JSON

{
urls: "http://*:5005"
}

7 Observação

UseConfiguration somente copia as chaves do IConfiguration fornecido para a


configuração do construtor de host. Portanto, a configuração reloadOnChange: true
para JSarquivos de configurações ON, INI e XML não tem 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 urls valor do hostsettings.json arquivo e o servidor escuta na
porta 8080:
CLI do .NET

dotnet run --urls "http://*:8080"

Gerenciar o host
Executar

O método Run inicia o aplicativo Web e bloqueia o thread de chamada até que o host
seja desligado:

C#

host.Run();

Iniciar

Execute o host sem bloqueio, chamando seu método Start :

C#

using (host)
{
host.Start();
Console.ReadLine();
}

Se uma lista de URLs for passada para o método Start , ele escutará nas URLs
especificadas:

C#

var urls = new List<string>()


{
"http://*:5000",
"http://localhost:5001"
};

var host = new WebHostBuilder()


.UseKestrel()
.UseStartup<Startup>()
.Start(urls.ToArray());

using (host)
{
Console.ReadLine();
}
O aplicativo pode inicializar e iniciar um novo host usando os padrões pré-configurados
de CreateDefaultBuilder , usando um método estático conveniente. Esses métodos
iniciam o servidor sem saída do console e com WaitForShutdown a espera de uma
interrupção (Ctrl-C/SIGINT ou SIGTERM):

Start(RequestDelegate app)

Inicie com um RequestDelegate :

C#

using (var host = WebHost.Start(app => app.Response.WriteAsync("Hello,


World!")))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Faça uma solicitação no navegador para http://localhost:5000 receber os blocos de


resposta "Olá, Mundo!" WaitForShutdown até que uma interrupção (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 :

C#

using (var host = WebHost.Start("http://localhost:8080", app =>


app.Response.WriteAsync("Hello, World!")))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Produz o mesmo resultado que Start(RequestDelegate app), mas o aplicativo responde


em http://localhost:8080 .

Start(Action<IRouteBuilder> routeBuilder)

Use uma instância de IRouteBuilder (Microsoft.AspNetCore.Routing ) para usar o


middleware de roteamento:

C#
using (var host = WebHost.Start(router => router
.MapGet("hello/{name}", (req, res, data) =>
res.WriteAsync($"Hello, {data.Values["name"]}!"))
.MapGet("buenosdias/{name}", (req, res, data) =>
res.WriteAsync($"Buenos dias, {data.Values["name"]}!"))
.MapGet("throw/{message?}", (req, res, data) =>
throw new Exception((string)data.Values["message"] ?? "Uh oh!"))
.MapGet("{greeting}/{name}", (req, res, data) =>
res.WriteAsync($"{data.Values["greeting"]},
{data.Values["name"]}!"))
.MapGet("", (req, res, data) => res.WriteAsync("Hello, World!"))))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Use as seguintes solicitações de navegador com o exemplo:

Solicitação Resposta

http://localhost:5000/hello/Martin Hello, Martin!

http://localhost:5000/buenosdias/Catrina Buenos dias, Catrina!

http://localhost:5000/throw/ooops! Gera uma exceção com a cadeia de caracteres


"ooops!"

http://localhost:5000/throw Gera uma exceção com a cadeia de caracteres "Uh


oh!"

http://localhost:5000/Sante/Kevin Sante, Kevin!

http://localhost:5000 Olá, Mundo!

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, Action<IRouteBuilder> routeBuilder)

Use uma URL e uma instância de IRouteBuilder :

C#

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

Produz o mesmo resultado que Start(Action<IRouteBuilder> routeBuilder), mas o


aplicativo responde em http://localhost:8080 .

StartWith(Action<IApplicationBuilder> app)

Forneça um delegado para configurar um IApplicationBuilder :

C#

using (var host = WebHost.StartWith(app =>


app.Use(next =>
{
return async context =>
{
await context.Response.WriteAsync("Hello World!");
};
})))
{
Console.WriteLine("Use Ctrl-C to shut down the host...");
host.WaitForShutdown();
}

Faça uma solicitação no navegador para http://localhost:5000 receber os blocos de


resposta "Olá, Mundo!" WaitForShutdown até que uma interrupção (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 :

C#

using (var host = WebHost.StartWith("http://localhost:8080", app =>


app.Use(next =>
{
return async context =>
{
await context.Response.WriteAsync("Hello World!");
};
})))
{
Console.WriteLine("Use Ctrl-C to shut down the host...");
host.WaitForShutdown();
}

Produz o mesmo resultado que StartWith(Action<IApplicationBuilder> app), mas o


aplicativo responde em http://localhost:8080 .

Interface IWebHostEnvironment
A IWebHostEnvironment interface fornece informações sobre o ambiente de hospedagem
da Web do aplicativo. Use a injeção de construtor para obter o IWebHostEnvironment
para usar suas propriedades e métodos de extensão:

C#

public class CustomFileReader


{
private readonly IWebHostEnvironment _env;

public CustomFileReader(IWebHostEnvironment env)


{
_env = env;
}

public string ReadFile(string filePath)


{
var fileProvider = _env.WebRootFileProvider;
// Process the file here
}
}

Uma abordagem baseada em convenção pode ser usada para configurar o aplicativo na
inicialização com base no ambiente. Como alternativa, injete o IWebHostEnvironment no
construtor Startup para uso em ConfigureServices :

C#

public class Startup


{
public Startup(IWebHostEnvironment env)
{
HostingEnvironment = env;
}

public IWebHostEnvironment HostingEnvironment { get; }


public void ConfigureServices(IServiceCollection services)
{
if (HostingEnvironment.IsDevelopment())
{
// Development configuration
}
else
{
// Staging/Production configuration
}

var contentRootPath = HostingEnvironment.ContentRootPath;


}
}

7 Observação

Além do método de extensão IsDevelopment , IWebHostEnvironment oferece os


métodos IsStaging , IsProduction e IsEnvironment(string environmentName) . Para
obter mais informações, confira Usar vários ambientes no ASP.NET Core.

O serviço IWebHostEnvironment também pode ser injetado diretamente no método


Configure para configurar o pipeline de processamento:

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
if (env.IsDevelopment())
{
// In Development, use the Developer Exception Page
app.UseDeveloperExceptionPage();
}
else
{
// In Staging/Production, route exceptions to /error
app.UseExceptionHandler("/error");
}

var contentRootPath = env.ContentRootPath;


}

IWebHostEnvironment pode ser injetado no método Invoke ao criar um middleware

personalizado:

C#
public async Task Invoke(HttpContext context, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
// Configure middleware for Development
}
else
{
// Configure middleware for Staging/Production
}

var contentRootPath = env.ContentRootPath;


}

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

Token de Disparado quando...


cancelamento

ApplicationStarted O host foi iniciado totalmente.

ApplicationStopped O host está concluindo um desligamento normal. Todas as solicitações


devem ser processadas. O desligamento é bloqueado até que esse evento
seja concluído.

ApplicationStopping O host está executando um desligamento normal. Solicitações ainda


podem estar sendo processadas. O desligamento é bloqueado até que
esse evento seja concluído.

C#

public class Startup


{
public void Configure(IApplicationBuilder app, IHostApplicationLifetime
appLifetime)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);

Console.CancelKeyPress += (sender, eventArgs) =>


{
appLifetime.StopApplication();
// Don't terminate the process immediately, wait for the Main
thread to exit gracefully.
eventArgs.Cancel = true;
};
}

private void OnStarted()


{
// Perform post-startup activities here
}

private void OnStopping()


{
// Perform on-stopping activities here
}

private void OnStopped()


{
// Perform post-stopped activities here
}
}

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:

C#

public class MyClass


{
private readonly IHostApplicationLifetime _appLifetime;

public MyClass(IHostApplicationLifetime appLifetime)


{
_appLifetime = appLifetime;
}

public void Shutdown()


{
_appLifetime.StopApplication();
}
}

Validação de escopo
CreateDefaultBuilder define ServiceProviderOptions.ValidateScopes como true se o
ambiente do aplicativo for 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 sempre validar escopos, inclusive no ambiente de produção, configure o


ServiceProviderOptions com UseDefaultServiceProvider no construtor de host:

C#

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
Implementações de servidor Web em
ASP.NET Core
Artigo • 28/11/2022 • 10 minutos para o fim da leitura

Por Tom Dykstra , Steve Smith , Stephen Halter e Chris Ross

Um aplicativo ASP.NET Core é executado com uma implementação do servidor HTTP


em processo. A implementação do servidor escuta solicitações HTTP e apresenta-as
para o aplicativo como um conjunto de recursos de solicitação compostos em um
HttpContext.

Windows

O ASP.NET Core vem com os seguintes itens:

Kestrel O servidor é a implementação padrão do servidor HTTP


multiplataforma. Kestrel fornece o melhor desempenho e a utilização de
memória, mas não tem alguns dos recursos avançados em HTTP.sys. Para
obter mais informações, consulte Kestrel vs HTTP.sys na próxima seção.
O servidor HTTP do IIS é um servidor em processo do IIS.
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:

No mesmo processo que o processo de trabalho do IIS (o modelo de


hospedagem em processo) com o servidor HTTP do IIS. Em processo é a
configuração recomendada.
Em um processo separado do processo de trabalho do IIS (o modelo de
hospedagem fora do processo) com o servidor Kestrel.

O módulo do ASP.NET Core é um módulo nativo do IIS que manipula as


solicitações nativas do IIS entre o IIS e o servidor HTTP do IIS em processo ou o
Kestrel. Para obter mais informações, confira Módulo do ASP.NET Core (ANCM)
para o IIS.

Kestrel vs HTTP.sys
Kestrel tem as seguintes vantagens sobre HTTP.sys:

Melhor desempenho e utilização de memória.


Plataforma cruzada
Agilidade, ele é desenvolvido e corrigido independentemente do sistema
operacional.
Configuração de TLS e porta programática
Extensibilidade que permite protocolos como PPv2 e transportes
alternativos.

Http.Sys opera como um componente de modo kernel compartilhado com os


seguintes recursos que o Kestrel não tem:

Compartilhamento de porta
Autenticação do Windows no modo kernel. Kestrel dá suporte apenas à
autenticação de modo de usuário.
Proxy rápido por meio de transferências de fila
Transmissão direta de arquivo
Cache de resposta

Modelos de hospedagem
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).

Usando a hospedagem fora do processo, os aplicativos do ASP.NET Core são


executados em um processo separado do processo de trabalho do IIS e o módulo
cuida do 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 uso de um processo separado
também permite hospedar mais de um aplicativo do mesmo pool de aplicativos.

Para obter mais informações e orientação sobre a configuração, consulte os


seguintes tópicos:

Hospedar o ASP.NET Core no Windows com o IIS


Módulo do ASP.NET Core(ANCM) para IIS
Kestrel
Kestrel O servidor é a implementação padrão do servidor HTTP multiplataforma. Kestrel
fornece o melhor desempenho e a utilização de memória, mas não tem alguns dos
recursos avançados em HTTP.sys. Para obter mais informações, consulte Kestrel vs
HTTP.sys neste documentação.

Use Kestrel:

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.

Para obter diretrizes de configuração de Kestrel e informações sobre quando usar


Kestrel em uma configuração de proxy reverso, consulte 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, veja Hospedar o ASP.NET Core no Linux com o Nginx.

Apache com Kestrel


Para obter informações sobre como usar Apache no Linux como um servidor proxy
reverso para Kestrel, veja Hospedar 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. Kestrel é recomendado em HTTP.sys, a menos que o aplicativo
exija recursos não disponíveis.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 diretrizes de configuração do HTTP.sys, consulte Implementação do servidor


Web do HTTP.sys no ASP.NET Core.

Infraestrutura de servidor do ASP.NET Core


O IApplicationBuilder disponível no método Startup.Configure expõe a propriedade
ServerFeatures do tipo IFeatureCollection. O Kestrel e o HTTP.sys expõem apenas um
único recurso cada, o IServerAddressesFeature, mas diferentes implementações de
servidor podem expor funcionalidades adicionais.

IServerAddressesFeature pode ser usado para descobrir a qual porta a implementação

do servidor se acoplou durante o runtime.

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 com base em NowinIServer. Somente as
interfaces de recurso que o aplicativo usa exigem implementação, embora no mínimo
IHttpRequestFeature e IHttpResponseFeature devam ser compatíveis.

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 .

Um arquivo launchSettings.json fornece configuração ao iniciar um aplicativo com


dotnet run ou com um depurador interno em ferramentas, como o Visual Studio. Se os
perfis de inicialização estiverem presentes em um arquivo launchSettings.json , use a
opção --launch-profile {PROFILE NAME} com o comando dotnet run ou selecione o
perfil no Visual Studio. Para obter mais informações, confira dotnet run e pacote de
distribuição do .NET Core.

Suporte do HTTP/2
O HTTP/2 é compatível com ASP.NET Core nos seguintes cenários de implantação:

Kestrel
Sistema operacional
Windows Server 2016/Windows 10 ou posterior†
Linux com OpenSSL 1.0.2 ou posterior (por exemplo, Ubuntu 16.04 ou
posterior)
O HTTP/2 será compatível com macOS em uma versão futura.
Estrutura de destino: .NET Core 2.2 ou posterior
HTTP.sys
Windows Server 2016/Windows 10 ou posterior
Estrutura de destino: não aplicável a implantações de HTTP.sys.
IIS (em processo)
Windows Server 2016/Windows 10 ou posterior; IIS 10 ou posterior
Estrutura de destino: .NET Core 2.2 ou posterior
IIS (fora do processo)
Windows Server 2016/Windows 10 ou posterior; IIS 10 ou posterior
Conexões de servidor de borda voltadas para o público usam HTTP/2, mas a
conexão de proxy reverso para o Kestrel usa HTTP/1.1.
Estrutura de destino: não aplicável a implantações IIS fora de processo.

†Kestrel tem suporte limitado para HTTP/2 no Windows Server 2012 R2 e Windows 8.1.
O suporte é limitado porque a lista de conjuntos de codificação TLS disponível nesses
sistemas operacionais é limitada. Um certificado gerado usando um ECDSA (Algoritmo
de Assinatura Digital Curva Elíptica) pode ser necessário para proteger conexões TLS.

Uma conexão HTTP/2 precisa usar ALPN (Application-Layer Protocol Negotiation) e


TLS 1.2 ou posterior. Para obter mais informações, consulte os tópicos referentes aos
seus cenários de implantação do servidor.

Recursos adicionais
Implementação do servidor Web Kestrel no ASP.NET Core
Módulo do ASP.NET Core(ANCM) para IIS
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
Configuração no ASP.NET Core
Artigo • 15/01/2023 • 87 minutos para o fim da leitura

Por Rick Anderson e Kirk Larkin

A configuração de aplicativos no ASP.NET Core é realizada usando um ou mais


provedores de configuração. Os provedores de configuração leem os dados de
configuração de pares chave-valor usando uma variedade de fontes de configuração:

Arquivos de configurações, como appsettings.json


Variáveis de ambiente
Cofre de Chave do Azure
Configuração de Aplicativo do Azure
Argumentos de linha de comando
Provedores personalizados, instalados ou criados
Arquivos de diretório
Objetos do .NET na memória

Este artigo fornece informações sobre configuração no ASP.NET Core. Para obter
informações sobre como usar a configuração em aplicativos de console, confira
Configuração do .NET.

Configuração de aplicativos e hosts


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. Os modelos ASP.NET
Core criam um WebApplicationBuilder que contém o host. Embora algumas
configurações possam ser feitas tanto no host quanto nos provedores de configuração
de aplicativos, em geral, apenas a configuração necessária para o host deve ser feita na
configuração do host.

A configuração de aplicativos é a prioridade mais alta e é detalhada na próxima seção. A


configuração de hosts segue a configuração de aplicativos e é descrita neste artigo.

Fontes de configuração de aplicativos padrão


Os aplicativos Web ASP.NET Core criados com dotnet new ou Visual Studio geram o
seguinte código:

C#
var builder = WebApplication.CreateBuilder(args);

O WebApplication.CreateBuilder inicializa uma nova instância da classe


WebApplicationBuilder com os padrões pré-configurados. O WebApplicationBuilder
( builder ) inicializado fornece a configuração padrão para o aplicativo na seguinte
ordem, da prioridade mais alta para a mais baixa:

1. Argumentos de linha de comando usando o provedor de configuração de linha de


comando.
2. Variáveis de ambiente não prefixadas usando o provedor de configuração de
variáveis de ambiente não prefixadas.
3. Segredos do usuário quando o aplicativo é executado no ambiente Development .
4. appsettings.{Environment}.json usando o provedor de configuração JSON. Por
exemplo, appsettings.Production.json e appsettings.Development.json .
5. appsettings.json usando o provedor de configuração JSON.
6. Um fallback para a configuração do host descrita na próxima seção.

Fontes de configuração de host padrão


A lista a seguir contém as fontes de configuração de host padrão da prioridade mais
alta para a mais baixa para WebApplicationBuilder:

1. Argumentos de linha de comando usando o provedor de configuração de linha de


comando
2. Variáveis ​de ambiente prefixadas pelo DOTNET_ usando o provedor de configuração
de variáveis ​de ambiente.
3. Variáveis ​de ambiente prefixadas pelo ASPNETCORE_ usando o provedor de
configuração de variáveis ​de ambiente.

Para o Host Genérico do .NET e o Host da Web, as fontes de configuração de host


padrão da prioridade mais alta para a mais baixa são:

1. Variáveis ​de ambiente prefixadas pelo ASPNETCORE_ usando o provedor de


configuração de variáveis ​de ambiente.
2. Argumentos de linha de comando usando o provedor de configuração de linha de
comando
3. Variáveis ​de ambiente prefixadas pelo DOTNET_ usando o provedor de configuração
de variáveis ​de ambiente.

Quando um valor de configuração é definido na configuração do host e do aplicativo, a


configuração do aplicativo é usada.
Variáveis do host
As seguintes variáveis são bloqueadas antecipadamente ao inicializar os construtores de
host e não podem ser influenciadas pela configuração do aplicativo:

Nome do aplicativo
Nome do ambiente, por exemplo Development , Production e Staging
Raiz do conteúdo
Raiz da Web
Se deve verificar se há assemblies de inicialização de hospedagem e quais
assemblies verificar.
Variáveis lidas por aplicativo e código de biblioteca de
HostBuilderContext.Configuration em retornos de chamada
IHostBuilder.ConfigureAppConfiguration.

Todas as outras configurações de host são lidas da configuração do aplicativo em vez da


configuração do host.

URLS é uma das muitas configurações de host comuns que não é uma configuração de
inicialização. Como todas as outras configurações de host que não estão na lista
anterior, URLS é lida posteriormente na configuração do aplicativo. A configuração do
host é um fallback para a configuração do aplicativo, portanto, a configuração do host
pode ser usada para definir URLS , mas será substituída por qualquer fonte de
configuração na configuração do aplicativo como appsettings.json .

Para obter mais informações, confira Alterar a raiz do conteúdo, o nome do aplicativo e
o ambiente e Alterar a raiz do conteúdo, o nome do aplicativo e o ambiente por
variáveis de ambiente ou linha de comando

As seções restantes neste artigo referem-se à configuração de aplicativos.

Provedores de configuração de aplicativos


O código a seguir exibe os provedores de configuração habilitados na ordem em que
foram adicionados:

C#

public class Index2Model : PageModel


{
private IConfigurationRoot ConfigRoot;

public Index2Model(IConfiguration configRoot)


{
ConfigRoot = (IConfigurationRoot)configRoot;
}

public ContentResult OnGet()


{
string str = "";
foreach (var provider in ConfigRoot.Providers.ToList())
{
str += provider.ToString() + "\n";
}

return Content(str);
}
}

A lista anterior das fontes de configuração padrão de prioridade mais alta para a mais
baixa mostra os provedores na ordem oposta em que são adicionados ao aplicativo
gerado pelo modelo. Por exemplo, o provedor de configuração JSON é adicionado
antes do provedor de configuração de linha de comando.

Os provedores de configuração adicionados posteriormente têm prioridade mais alta e


substituem as configurações de chave anteriores. Por exemplo, se MyKey estiver
configurado em appsettings.json e no ambiente, o valor do ambiente será usado.
Usando os provedores de configuração padrão, o provedor de configuração de linha de
comando substitui todos os outros provedores.

Para obter mais informações sobre CreateBuilder , confira Configurações do construtor


padrão.

appsettings.json

Considere o seguinte arquivo appsettings.json :

JSON

{
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
},
"MyKey": "My appsettings.json Value",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

O código a seguir do download de exemplo exibe várias das configurações anteriores:

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

O padrão JsonConfigurationProvider carrega a configuração na seguinte ordem:

1. appsettings.json
2. appsettings.{Environment}.json : por exemplo, os arquivos
appsettings.Production.json e appsettings.Development.json . A versão do

ambiente do arquivo é carregada com base no


IHostingEnvironment.EnvironmentName. Para obter mais informações, confira Usar
vários ambientes no ASP.NET Core.

Os valores de appsettings.{Environment}.json substituem as chaves em


appsettings.json . Por exemplo, por padrão:

No desenvolvimento, a configuração de appsettings.Development.json substitui os


valores encontrados em appsettings.json .
Na produção, a configuração de appsettings.Production.json substitui os valores
encontrados em appsettings.json . Por exemplo, ao implantar o aplicativo no
Azure.

Se um valor de configuração deve ser assegurado, confira GetValue. O exemplo anterior


lê apenas cadeias de caracteres e não dá suporte a um valor padrão.

Usando a configuração padrão, os arquivos appsettings.json e appsettings.


{Environment}.json são habilitados com reloadOnChange: true . As alterações feitas
no arquivo appsettings.json e appsettings.{Environment}.json após a inicialização do
aplicativo são lidas pelo provedor de configuração ONJS.

Comentários em appsettings.json
Há suporte para comentários em appsettings.json arquivos e appsettings.
{Environment}.json usando comentários de estilo JavaScript ou C#.

Vincular dados de configuração hierárquica usando o


padrão de opções
A maneira preferencial de ler os valores de configuração relacionados é usando o
padrão de opções. Por exemplo, para ler os seguintes valores de configuração:

JSON

"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}

Crie a seguinte classe PositionOptions :

C#

public class PositionOptions


{
public const string Position = "Position";

public string Title { get; set; } = String.Empty;


public string Name { get; set; } = String.Empty;
}

Uma classe de opções:

Deve ser não abstrata e com um construtor público sem parâmetros.


Todas as propriedades públicas de leitura e gravação do tipo são vinculadas.
Os campos não são vinculados. No código anterior, Position não está vinculado.
O campo Position é usado para que a cadeia de caracteres "Position" não
precise ser codificada no aplicativo ao associar a classe a um provedor de
configuração.

O seguinte código:

Chama ConfigurationBinder.Bind para associar a classe PositionOptions à seção


Position .
Exibe os dados de configuração de Position .

C#

public class Test22Model : PageModel


{
private readonly IConfiguration Configuration;

public Test22Model(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var positionOptions = new PositionOptions();

Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

return Content($"Title: {positionOptions.Title} \n" +


$"Name: {positionOptions.Name}");
}
}

No código anterior, por padrão, as alterações no arquivo de configuração JSON após a


inicialização do aplicativo são lidas.

ConfigurationBinder.Get<T> associa e retorna o tipo especificado.


ConfigurationBinder.Get<T> pode ser mais conveniente do que usar

ConfigurationBinder.Bind . O código a seguir mostra como usar


ConfigurationBinder.Get<T> com a classe PositionOptions :

C#

public class Test21Model : PageModel


{
private readonly IConfiguration Configuration;
public PositionOptions? positionOptions { get; private set; }
public Test21Model(IConfiguration configuration)
{
Configuration = configuration;
}

public ContentResult OnGet()


{
positionOptions = Configuration.GetSection(PositionOptions.Position)
.Get<PositionOptions>
();

return Content($"Title: {positionOptions.Title} \n" +


$"Name: {positionOptions.Name}");
}
}

No código anterior, por padrão, as alterações no arquivo de configuração JSON após a


inicialização do aplicativo são lidas.

Uma abordagem alternativa ao usar o padrão de opções é associar a seção Position e


adicioná-la ao contêiner do serviço de injeção de dependência. No código a seguir,
PositionOptions é adicionada ao contêiner de serviço com Configure e associada à

configuração:

C#

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

Usando o código anterior, o código a seguir lê as opções de posição:

C#

public class Test2Model : PageModel


{
private readonly PositionOptions _options;

public Test2Model(IOptions<PositionOptions> options)


{
_options = options.Value;
}
public ContentResult OnGet()
{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}

No código anterior, as alterações no arquivo de configuração JSON após a inicialização


do aplicativo não são lidas. Para ler as alterações após a inicialização do aplicativo, use
IOptionsSnapshot.

Usando a configuração padrão, os arquivos appsettings.json e appsettings.


{Environment}.json são habilitados com reloadOnChange: true . As alterações feitas
no arquivo appsettings.json e appsettings.{Environment}.json após a inicialização do
aplicativo são lidas pelo provedor de configuração ONJS.

Confira Provedor de configuração JSON neste documento para obter informações sobre
como adicionar arquivos de configuração JSON adicionais.

Como combinar a coleção de serviços


Considere o seguinte ao registrar serviços e configurar opções:

C#

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Grupos de registros relacionados podem ser movidos para um método de extensão


para registrar serviços. Por exemplo, os serviços de configuração são adicionados à
seguinte classe:
C#

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));

return services;
}

public static IServiceCollection AddMyDependencyGroup(


this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();

return services;
}
}
}

Os serviços restantes são registrados em uma classe similar. O código a seguir usa os
novos métodos de extensão para registrar os serviços:

C#

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Observação: Cada método de extensão services.Add{GROUP_NAME} adiciona e


possivelmente configura serviços. Por exemplo, AddControllersWithViews adiciona os
serviços que os controladores MVC com visualizações requerem e AddRazorPages
adiciona os serviços que o Razor Pages requer.

Segurança e segredos do usuário


Diretrizes de dados de configuração:

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. A
ferramenta Gerenciador de Segredos pode ser usada para armazenar segredos em
desenvolvimento.
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.

Por padrão, a fonte de configuração de segredos do usuário é registrada após as fontes


de configuração JSON. Portanto, as chaves de segredos do usuário têm precedência
sobre as chaves em appsettings.json e appsettings.{Environment}.json .

Para obter mais informações sobre como armazenar senhas ou outros dados
confidenciais:

Usar múltiplos ambientes no ASP.NET Core


Armazenamento seguro de segredos de aplicativo em desenvolvimento no
ASP.NET Core: inclui recomendações sobre o uso de variáveis de ambiente para
armazenar dados confidenciais. A ferramenta Gerenciador de Segredos usa o
provedor de configuração de arquivo para armazenar segredos de usuário em um
arquivo JSON no sistema local.

O Azure Key Vault armazena os segredos do aplicativo com segurança para aplicativos
ASP.NET Core. Para obter mais informações, confira Provedor de configuração do Azure
Key Vault no ASP.NET Core.

Variáveis de ambiente não prefixadas


Variáveis de ambiente não prefixadas são variáveis de ambiente diferentes daquelas
prefixadas por ASPNETCORE_ ou DOTNET_ . Por exemplo, os modelos de aplicativo Web
ASP.NET Core definem "ASPNETCORE_ENVIRONMENT": "Development" em
launchSettings.json . Para obter mais informações sobre variáveis de ambiente

ASPNETCORE_ e DOTNET_ , confira:


Lista de fontes de configuração padrão de prioridade mais alta para a mais baixa
incluindo variáveis de ambiente não prefixadas, prefixadas pelo ASPNETCORE_ e
prefixadas pelo DOTNETCORE_ .
Variáveis de ambiente DOTNET_ usadas fora de Microsoft.Extensions.Hosting.

Usando a configuração padrão, o EnvironmentVariablesConfigurationProvider carrega a


configuração dos pares chave-valor da variável de ambiente depois de ler
appsettings.json , appsettings.{Environment}.json , e segredos do usuário. Portanto, os
valores de chave lidos do ambiente substituem os valores lidos de appsettings.json ,
appsettings.{Environment}.json e segredos do usuário.

O separador : não funciona com chaves hierárquicas de variáveis de ambiente em


todas as plataformas. __ , o sublinhado duplo, tem:

Suporte em todas as plataformas. Por exemplo, o separador : não tem suporte


pelo Bash , mas pelo __ tem.
Substituição automática por um :

Os comandos set seguintes:

Defina as chaves de ambiente e os valores do exemplo anterior no Windows.


Teste as configurações ao usar o download de exemplo . O comando dotnet run
deve ser executado no diretório do projeto.

CLI do .NET

set MyKey="My key from Environment"


set Position__Title=Environment_Editor
set Position__Name=Environment_Rick
dotnet run

As configurações de ambiente anteriores:

São definidos apenas em processos iniciados a partir da janela de comando em


que foram definidos.
Não serão lidos por navegadores iniciados com o Visual Studio.

Os comandos setx a seguir podem ser usados para definir as chaves de ambiente e os
valores no Windows. Diferente de set , as configurações setx são persistentes. /M
define a variável no ambiente do sistema. Se o comutador /M não for usado, uma
variável de ambiente do usuário será definida.

Console
setx MyKey "My key from setx Environment" /M
setx Position__Title Environment_Editor /M
setx Position__Name Environment_Rick /M

Para testar se os comandos anteriores substituem appsettings.json e appsettings.


{Environment}.json :

Com o Visual Studio: saia e reinicie o Visual Studio.


Com a CLI: inicie uma nova janela de comando e insira dotnet run .

Chame AddEnvironmentVariables com uma cadeia de caracteres para especificar um


prefixo para variáveis de ambiente:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Configuration.AddEnvironmentVariables(prefix: "MyCustomPrefix_");

var app = builder.Build();

No código anterior:

builder.Configuration.AddEnvironmentVariables(prefix: "MyCustomPrefix_") é

adicionado após os provedores de configuração padrão. Para obter um exemplo


de ordenação dos provedores de configuração, confira o provedor de
configuração JSON.
As variáveis de ambiente definidas com o prefixo MyCustomPrefix_ substituem os
provedores de configuração padrão. Isso inclui variáveis de ambiente sem o
prefixo.

O prefixo é removido quando os pares de valores-chave de configuração são lidos.

Os comandos a seguir testam o prefixo personalizado:

CLI do .NET

set MyCustomPrefix_MyKey="My key with MyCustomPrefix_ Environment"


set MyCustomPrefix_Position__Title=Editor_with_customPrefix
set MyCustomPrefix_Position__Name=Environment_Rick_cp
dotnet run
A configuração padrão carrega variáveis de ambiente e argumentos de linha de
comando prefixados com DOTNET_ e ASPNETCORE_ . Os prefixos DOTNET_ e ASPNETCORE_
são usados pelo ASP.NET Core para configuração de host e aplicativo, mas não para
configuração de usuário. Para obter mais informações sobre configuração de host e
aplicativo, confira Host genérico do .NET.

Em Serviço de Aplicativo do Azure , selecione Nova configuração de aplicativo na


página Configuração > Configurações. As configurações do aplicativo do Serviço de
Aplicativo do Azure são:

Criptografadas em repouso e transmitidas por um canal criptografado.


Expostas como variáveis de ambiente.

Para saber mais, confira Aplicativos do Azure: substituir a configuração do aplicativo


usando o portal do Azure.

Confira Prefixos de cadeia de conexão para obter informações sobre cadeias de conexão
de banco de dados do Azure.

Nomenclatura de variáveis de ambiente


Os nomes das variáveis de ambiente refletem a estrutura de um arquivo
appsettings.json . Cada elemento na hierarquia é separado por um sublinhado duplo
(preferível) ou dois pontos. Quando a estrutura do elemento inclui uma matriz, o índice
da matriz deve ser tratado como um nome de elemento adicional neste caminho.
Considere o arquivo appsettings.json a seguir e seus valores equivalentes
representados como variáveis de ambiente.

appsettings.json

JSON

{
"SmtpServer": "smtp.example.com",
"Logging": [
{
"Name": "ToEmail",
"Level": "Critical",
"Args": {
"FromAddress": "MySystem@example.com",
"ToAddress": "SRE@example.com"
}
},
{
"Name": "ToConsole",
"Level": "Information"
}
]
}

variáveis de ambiente

Console

setx SmtpServer smtp.example.com


setx Logging__0__Name ToEmail
setx Logging__0__Level Critical
setx Logging__0__Args__FromAddress MySystem@example.com
setx Logging__0__Args__ToAddress SRE@example.com
setx Logging__1__Name ToConsole
setx Logging__1__Level Information

Variáveis de ambiente definidas no launchSettings.json


gerado
As variáveis de ambiente definidas em launchSettings.json substituem as definidas no
ambiente do sistema. Por exemplo, os modelos da Web do ASP.NET Core geram um
arquivo launchSettings.json que define a configuração do ponto de extremidade para:

JSON

"applicationUrl": "https://localhost:5001;http://localhost:5000"

A configuração de applicationUrl define a variável de ambiente ASPNETCORE_URLS e


substitui os valores definidos no ambiente.

Variáveis de ambiente de escape no Linux


No Linux, o valor das variáveis de ambiente de URL deve ser escapado para que systemd
possa analisá-lo. Use o ferramenta linux systemd-escape que suspende http:--
localhost:5001

cmd

groot@terminus:~$ systemd-escape http://localhost:5001


http:--localhost:5001

Exibir variáveis de ambiente


O código a seguir exibe as variáveis e valores de ambiente na inicialização do aplicativo,
o que pode ser útil ao depurar as configurações do ambiente:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

foreach (var c in builder.Configuration.AsEnumerable())


{
Console.WriteLine(c.Key + " = " + c.Value);
}

Linha de comando
Usando a configuração padrão, o CommandLineConfigurationProvider carrega a
configuração dos pares chave-valor do argumento da linha de comando após as
seguintes fontes de configuração:

Arquivos appsettings.json e appsettings.{Environment}.json .


Segredos do aplicativo no ambiente de desenvolvimento.
Variáveis de ambiente.

Por padrão, os valores de configuração definidos na linha de comando substituem os


valores de configuração definidos com todos os outros provedores de configuração.

Argumentos de linha de comando


O comando a seguir define chaves e valores usando = :

CLI do .NET

dotnet run MyKey="Using =" Position:Title=Cmd Position:Name=Cmd_Rick

O comando a seguir define chaves e valores usando / :

CLI do .NET

dotnet run /MyKey "Using /" /Position:Title=Cmd /Position:Name=Cmd_Rick

O comando a seguir define chaves e valores usando -- :

CLI do .NET
dotnet run --MyKey "Using --" --Position:Title=Cmd --Position:Name=Cmd_Rick

O valor da chave:

Deve seguir = , ou a chave deve ter um prefixo de -- ou / quando o valor vier


após um espaço.
Não é necessário se = for usado. Por exemplo, MySetting= .

No mesmo comando, não combine pares chave-valor do argumento de linha de


comando que usam um sinal de = l com pares chave-valor que usam um espaço.

Mapeamentos de comutador
Os mapeamentos de comutador permitem fornecer a lógica de substituição do nome
da chave. Forneça um dicionário de substituições de comutador 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 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 iniciar com - ou -- .


O dicionário de mapeamentos de comutador chave não deve conter chaves
duplicadas.

Para usar um dicionário de mapeamentos de comutador, passe-o para a chamada para


AddCommandLine :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var switchMappings = new Dictionary<string, string>()


{
{ "-k1", "key1" },
{ "-k2", "key2" },
{ "--alt3", "key3" },
{ "--alt4", "key4" },
{ "--alt5", "key5" },
{ "--alt6", "key6" },
};

builder.Configuration.AddCommandLine(args, switchMappings);

var app = builder.Build();

Execute o seguinte comando para testar a substituição da chave:

CLI do .NET

dotnet run -k1 value1 -k2 value2 --alt3=value2 /alt4=value3 --alt5 value5
/alt6 value6

O código a seguir mostra os valores de chave para as chaves substituídas:

C#

public class Test3Model : PageModel


{
private readonly IConfiguration Config;

public Test3Model(IConfiguration configuration)


{
Config = configuration;
}

public ContentResult OnGet()


{
return Content(
$"Key1: '{Config["Key1"]}'\n" +
$"Key2: '{Config["Key2"]}'\n" +
$"Key3: '{Config["Key3"]}'\n" +
$"Key4: '{Config["Key4"]}'\n" +
$"Key5: '{Config["Key5"]}'\n" +
$"Key6: '{Config["Key6"]}'");
}
}

Para aplicativos que usam mapeamentos de opção, a chamada CreateDefaultBuilder


para não deve passar argumentos. 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 . 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.

Definir ambiente e argumentos de linha de


comando com o Visual Studio
Os argumentos de ambiente e linha de comando podem ser definidos no Visual Studio
na caixa de diálogo de perfis de inicialização:

No Gerenciador de Soluções, clique com o botão direito do mouse no projeto e


selecione Propriedades.
Selecione Depurar > Geral e Abrir depuração dos perfis de inicialização da IU.

Dados de configuração hierárquica


A API de configuração lê os dados de configuração hierárquica nivelando os dados
hierárquicos com o uso de um delimitador nas chaves de configuração.

O download de exemplo contém o seguinte arquivo appsettings.json :

JSON

{
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
},
"MyKey": "My appsettings.json Value",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

O código a seguir do download de exemplo exibe várias das configurações:

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;
public TestModel(IConfiguration configuration)
{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

A maneira preferencial de ler dados de configuração hierárquica é usando o padrão de


opções. Para obter mais informações, confira Vincular dados de configuração
hierárquica neste documento.

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.

Teclas de configuração e valores


Teclas de configuração:

Não diferencia maiúsculas e minúsculas. Por exemplo, ConnectionString e


connectionstring são tratados como chaves equivalentes.

Se uma chave e um valor forem definidos em mais de um provedor de


configuração, será usado o valor do último provedor adicionado. Para obter mais
informações, confira Configuração padrão.
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 automaticamente em dois-pontos : .
No Azure Key Vault, as chaves hierárquicas usam -- como separador. O
provedor de configuração do Azure Key Vault substitui automaticamente --
por : quando os segredos são 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.

Valores de configuração:

são cadeias de caracteres.


Não é possível armazenar valores nulos na configuração ou associá-los a objetos.

Provedores de configuração
A tabela a seguir mostra os provedores de configuração disponíveis para aplicativos
ASP.NET Core.

Provedor Fornece a configuração de

Provedor de configuração do Azure Key Vault Cofre de Chave do Azure

Provedor de configuração do Aplicativo do Azure Configuração de Aplicativo do Azure

Provedor de configuração de linha de comando Parâmetros de linha de comando

Provedor de Configuração personalizado Fonte personalizada

Provedor de configuração de variáveis de ambiente Variáveis de ambiente

Provedor de configuração de arquivo Arquivos INI, JSON e XML

Provedor de configuração de chave por arquivo Arquivos de diretório

Provedor de configuração de memória Coleções na memória

Segredos do usuário Arquivo no diretório de perfil do usuário

As fontes de configuração são lidas na ordem especificada pelos provedores de


configuração. Solicite provedores de configuração em código para atender às
prioridades das fontes de configuração subjacentes exigidas pelo aplicativo.

Uma sequência comum de provedores de configuração é:

1. appsettings.json
2. appsettings.{Environment}.json
3. Segredos do usuário
4. Variáveis de ambiente usando o Provedor de configuração de variáveis de
ambiente.
5. Argumentos de linha de comando usando o Provedor de configuração de linha de
comando.

Uma prática comum é adicionar o provedor de configuração de linha de comando por


último em uma série de provedores, a fim de permitir que os argumentos de linha de
comando substituam a configuração definida por outros provedores.

A sequência anterior de provedores é usada na configuração padrão.

Prefixos de cadeia de conexão


A API de configuração tem regras de processamento especiais para quatro variáveis de
ambiente de cadeia de conexão. Essas cadeias de conexão estão envolvidas na
configuração de cadeias de conexão do Azure para o ambiente do aplicativo. As
variáveis de ambiente com os prefixos mostrados na tabela são carregadas no aplicativo
com a configuração padrão ou quando nenhum prefixo é fornecido ao
AddEnvironmentVariables .

Prefixo da cadeia de conexão Provedor

CUSTOMCONNSTR_ Provedor personalizado

MYSQLCONNSTR_ MySQL

SQLAZURECONNSTR_ Banco de Dados SQL do Azure

SQLCONNSTR_ SQL Server

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

Chave de variável de Chave de configuração Entrada de configuração do


ambiente convertida provedor

CUSTOMCONNSTR_{KEY} ConnectionStrings:{KEY} Entrada de configuração não criada.


Chave de variável de Chave de configuração Entrada de configuração do
ambiente convertida provedor

MYSQLCONNSTR_{KEY} ConnectionStrings:{KEY} Chave: ConnectionStrings:


{KEY}_ProviderName :
Valor: MySql.Data.MySqlClient

SQLAZURECONNSTR_{KEY} ConnectionStrings:{KEY} Chave: ConnectionStrings:


{KEY}_ProviderName :
Valor: System.Data.SqlClient

SQLCONNSTR_{KEY} ConnectionStrings:{KEY} Chave: ConnectionStrings:


{KEY}_ProviderName :
Valor: System.Data.SqlClient

Provedor de configuração de arquivo


FileConfigurationProvider é a classe base para carregamento da configuração do
sistema de arquivos. Os seguintes provedores de configuração derivam de
FileConfigurationProvider :

Provedor de configuração INI


Provedor de configuração JSON
Provedor de configuração XML

Provedor de configuração INI


O IniConfigurationProvider carrega a configuração de pares chave-valor do arquivo INI
em runtime.

O código a seguir limpa todos os provedores de configuração e adiciona vários


provedores de configuração:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAppConfiguration((hostingContext, config) =>


{
config.Sources.Clear();

var env = hostingContext.HostingEnvironment;

config.AddIniFile("MyIniConfig.ini", optional: true, reloadOnChange:


true)
.AddIniFile($"MyIniConfig.{env.EnvironmentName}.ini",
optional: true, reloadOnChange: true);

config.AddEnvironmentVariables();

if (args != null)
{
config.AddCommandLine(args);
}
});

builder.Services.AddRazorPages();

var app = builder.Build();

No código anterior, as configurações nos arquivos MyIniConfig.ini e MyIniConfig.


{Environment}.ini são substituídas pelas configurações no:

Provedor de configuração de variáveis de ambiente


Provedor de configuração de linha de comando.

O download de exemplo contém o seguinte arquivo MyIniConfig.ini :

ini

MyKey="MyIniConfig.ini Value"

[Position]
Title="My INI Config title"
Name="My INI Config name"

[Logging:LogLevel]
Default=Information
Microsoft=Warning

O código a seguir do download de exemplo exibe várias das configurações anteriores:

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

Provedor de configuração JSON


O JsonConfigurationProvider carrega a configuração dos pares de valores-chave do
arquivo JSON.

As sobrecargas podem especificar:

Se o arquivo é opcional.
Se a configuração será recarregada caso o arquivo seja alterado.

Considere o seguinte código:

C#

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("MyConfig.json",
optional: true,
reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

O código anterior:

Configura o provedor de configuração JSON para carregar o arquivo


MyConfig.json com as seguintes opções:

optional: true : o arquivo é opcional.


reloadOnChange: true : o arquivo é recarregado quando as alterações são

salvas.
Lê os provedores de configuração padrão antes do arquivo MyConfig.json . As
configurações no arquivo MyConfig.json substituem a configuração nos
provedores de configuração padrão, incluindo o provedor de configuração de
variáveis de ambiente e o provedor de configuração de linha de comando.

Normalmente, você não deseja um arquivo JSON personalizado substituindo os valores


definidos no provedor de configuração de variáveis de ambiente e no provedor de
configuração de linha de comando.

Provedor de configuração XML


O XmlConfigurationProvider carrega a configuração de pares chave-valor do arquivo
XML em runtime.

O código a seguir limpa todos os provedores de configuração e adiciona vários


provedores de configuração:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAppConfiguration((hostingContext, config) =>


{
config.Sources.Clear();

var env = hostingContext.HostingEnvironment;

config.AddXmlFile("MyXMLFile.xml", optional: true, reloadOnChange: true)


.AddXmlFile($"MyXMLFile.{env.EnvironmentName}.xml",
optional: true, reloadOnChange: true);

config.AddEnvironmentVariables();

if (args != null)
{
config.AddCommandLine(args);
}
});

builder.Services.AddRazorPages();

var app = builder.Build();

No código anterior, as configurações nos arquivos MyXMLFile.xml e MyXMLFile.


{Environment}.xml são substituídas pelas configurações no:

Provedor de configuração de variáveis de ambiente


Provedor de configuração de linha de comando.

O download de exemplo contém o seguinte arquivo MyXMLFile.xml :

XML

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<MyKey>MyXMLFile Value</MyKey>
<Position>
<Title>Title from MyXMLFile</Title>
<Name>Name from MyXMLFile</Name>
</Position>
<Logging>
<LogLevel>
<Default>Information</Default>
<Microsoft>Warning</Microsoft>
</LogLevel>
</Logging>
</configuration>

O código a seguir do download de exemplo exibe várias das configurações anteriores:

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}
Elementos repetidos que usam o mesmo nome de elemento funcionarão se o atributo
name for usado para diferenciar os elementos:

XML

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<section name="section0">
<key name="key0">value 00</key>
<key name="key1">value 01</key>
</section>
<section name="section1">
<key name="key0">value 10</key>
<key name="key1">value 11</key>
</section>
</configuration>

O código a seguir lê o arquivo de configuração anterior e exibe as chaves e os valores:

C#

public class IndexModel : PageModel


{
private readonly IConfiguration Configuration;

public IndexModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var key00 = "section:section0:key:key0";
var key01 = "section:section0:key:key1";
var key10 = "section:section1:key:key0";
var key11 = "section:section1:key:key1";

var val00 = Configuration[key00];


var val01 = Configuration[key01];
var val10 = Configuration[key10];
var val11 = Configuration[key11];

return Content($"{key00} value: {val00} \n" +


$"{key01} value: {val01} \n" +
$"{key10} value: {val10} \n" +
$"{key10} value: {val11} \n"
);
}
}

É possível usar atributos para fornecer valores:


XML

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<key attribute="value" />
<section>
<key attribute="value" />
</section>
</configuration>

O arquivo de configuração anterior carrega as seguintes chaves com value :

key:attribute
section:key:attribute

Provedor de configuração de chave por arquivo


O KeyPerFileConfigurationProvider usa arquivos do diretório como pares chave-valor de
configuração. A chave é o nome do arquivo. O valor contém o conteúdo do arquivo. O
provedor de configuração de chave por arquivo é usado em cenários de hospedagem
do Docker.

Para ativar a configuração de chave por arquivo, chame o método de extensão


AddKeyPerFile em uma instância do ConfigurationBuilder. O directoryPath para os
arquivos deve ser um caminho absoluto.

As sobrecargas permitem especificar:

Um delegado Action<KeyPerFileConfigurationSource> que configura a origem.


Se o diretório é opcional, e o caminho para o diretório.

O sublinhado duplo ( __ ) é usado como um delimitador de chave de configuração em


nomes de arquivo. Por exemplo, o nome do arquivo Logging__LogLevel__System produz
a chave de configuração Logging:LogLevel:System .

Chame ConfigureAppConfiguration ao criar o host para especificar a configuração do


aplicativo:

C#

.ConfigureAppConfiguration((hostingContext, config) =>


{
var path = Path.Combine(
Directory.GetCurrentDirectory(), "path/to/files");
config.AddKeyPerFile(directoryPath: path, optional: true);
})
Provedor de configuração de memória
O MemoryConfigurationProvider usa uma coleção na memória como pares chave-valor
de configuração.

O código a seguir adiciona uma coleção de memória ao sistema de configuração:

C#

var builder = WebApplication.CreateBuilder(args);

var Dict = new Dictionary<string, string>


{
{"MyKey", "Dictionary MyKey Value"},
{"Position:Title", "Dictionary_Title"},
{"Position:Name", "Dictionary_Name" },
{"Logging:LogLevel:Default", "Warning"}
};

builder.Host.ConfigureAppConfiguration((hostingContext, config) =>


{
config.Sources.Clear();

config.AddInMemoryCollection(Dict);

config.AddEnvironmentVariables();

if (args != null)
{
config.AddCommandLine(args);
}
});

builder.Services.AddRazorPages();

var app = builder.Build();

O código a seguir do download de exemplo exibe as configurações anteriores:

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

No código anterior, config.AddInMemoryCollection(Dict) é adicionado após os


provedores de configuração padrão. Para obter um exemplo de ordenação dos
provedores de configuração, confira o provedor de configuração JSON.

Confira Associar uma matriz para outro exemplo usando MemoryConfigurationProvider .

configuração do ponto de extremidade Kestrel


configuração de ponto de extremidade específica Kestrel substitui todas as
configurações de ponto de extremidade entre servidores. As configurações de ponto de
extremidade entre servidores incluem:

UseUrls
--urls na linha de comando

A variável de ambiente ASPNETCORE_URLS

Considere o seguinte arquivo appsettings.json usado em um aplicativo Web ASP.NET


Core:

JSON

{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://localhost:9999"
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Quando a marcação realçada anterior é usada em um aplicativo Web ASP.NET Core e o


aplicativo é iniciado na linha de comando com a seguinte configuração de ponto de
extremidade entre servidores:

dotnet run --urls="https://localhost:7777"

Kestrel se liga ao ponto de extremidade configurado especificamente para Kestrel no


arquivo appsettings.json ( https://localhost:9999 ) e não no https://localhost:7777 .

Considere o ponto de extremidade específico Kestrel configurado como uma variável de


ambiente:

set Kestrel__Endpoints__Https__Url=https://localhost:8888

Na variável de ambiente anterior, Https é o nome do ponto de extremidade específico


do Kestrel. O arquivo appsettings.json anterior também define um ponto de
extremidade específico Kestrel chamado Https . Por padrão, as variáveis de ambiente
que usam o provedor de configuração de variáveis de ambiente são lidas após
appsettings.{Environment}.json , portanto, a variável de ambiente anterior é usada para
o ponto de extremidade Https .

GetValue
ConfigurationBinder.GetValue extrai um valor de configuração com uma chave
especificada e o converte para o tipo especificado:

C#

public class TestNumModel : PageModel


{
private readonly IConfiguration Configuration;

public TestNumModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var number = Configuration.GetValue<int>("NumberKey", 99);
return Content($"{number}");
}
}

No código anterior, se NumberKey não for encontrado na configuração, será usado o


valor padrão 99 .

GetSection, GetChildren e Exists


Para os exemplos a seguir, considere o seguinte arquivo MySubsection.json :

JSON

{
"section0": {
"key0": "value00",
"key1": "value01"
},
"section1": {
"key0": "value10",
"key1": "value11"
},
"section2": {
"subsection0": {
"key0": "value200",
"key1": "value201"
},
"subsection1": {
"key0": "value210",
"key1": "value211"
}
}
}

O código a seguir adiciona MySubsection.json aos provedores de configuração:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAppConfiguration((hostingContext, config) =>


{
config.AddJsonFile("MySubsection.json",
optional: true,
reloadOnChange: true);
});
builder.Services.AddRazorPages();

var app = builder.Build();

GetSection
IConfiguration.GetSection returna uma subseção de configuração com a chave de
subseção especificada.

O código a seguir retorna valores para section1 :

C#

public class TestSectionModel : PageModel


{
private readonly IConfiguration Config;

public TestSectionModel(IConfiguration configuration)


{
Config = configuration.GetSection("section1");
}

public ContentResult OnGet()


{
return Content(
$"section1:key0: '{Config["key0"]}'\n" +
$"section1:key1: '{Config["key1"]}'");
}
}

O código a seguir retorna valores para section2:subsection0 :

C#

public class TestSection2Model : PageModel


{
private readonly IConfiguration Config;

public TestSection2Model(IConfiguration configuration)


{
Config = configuration.GetSection("section2:subsection0");
}

public ContentResult OnGet()


{
return Content(
$"section2:subsection0:key0 '{Config["key0"]}'\n" +
$"section2:subsection0:key1:'{Config["key1"]}'");
}
}
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 e Exists
O código a seguir chama IConfiguration.GetChildren e retorna valores para
section2:subsection0 :

C#

public class TestSection4Model : PageModel


{
private readonly IConfiguration Config;

public TestSection4Model(IConfiguration configuration)


{
Config = configuration;
}

public ContentResult OnGet()


{
string s = "";
var selection = Config.GetSection("section2");
if (!selection.Exists())
{
throw new Exception("section2 does not exist.");
}
var children = selection.GetChildren();

foreach (var subSection in children)


{
int i = 0;
var key1 = subSection.Key + ":key" + i++.ToString();
var key2 = subSection.Key + ":key" + i.ToString();
s += key1 + " value: " + selection[key1] + "\n";
s += key2 + " value: " + selection[key2] + "\n";
}
return Content(s);
}
}

O código anterior chama ConfigurationExtensions.Exists para verificar se a seção existe:

Associar uma matriz


O ConfigurationBinder.Bind dá suporte a matrizes de associação para objetos usando os
índices em chaves de configuração. Qualquer formato de matriz que exponha um
segmento de chave numérica é capaz de associar matrizes a uma matriz de classe
POCO .

Considere MyArray.json do download de exemplo :

JSON

{
"array": {
"entries": {
"0": "value00",
"1": "value10",
"2": "value20",
"4": "value40",
"5": "value50"
}
}
}

O código a seguir adiciona MyArray.json aos provedores de configuração:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAppConfiguration((hostingContext, config) =>


{
config.AddJsonFile("MyArray.json",
optional: true,
reloadOnChange: true); ;
});

builder.Services.AddRazorPages();

var app = builder.Build();

O código a seguir lê a configuração e exibe os valores:

C#

public class ArrayModel : PageModel


{
private readonly IConfiguration Config;
public ArrayExample? _array { get; private set; }

public ArrayModel(IConfiguration config)


{
Config = config;
}

public ContentResult OnGet()


{
_array = Config.GetSection("array").Get<ArrayExample>();
if (_array == null)
{
throw new ArgumentNullException(nameof(_array));
}
string s = String.Empty;

for (int j = 0; j < _array.Entries.Length; j++)


{
s += $"Index: {j} Value: {_array.Entries[j]} \n";
}

return Content(s);
}
}

C#

public class ArrayExample


{
public string[]? Entries { get; set; }
}

O código anterior retorna a saída a seguir:

text

Index: 0 Value: value00


Index: 1 Value: value10
Index: 2 Value: value20
Index: 3 Value: value40
Index: 4 Value: value50

Na saída anterior, o Índice 3 tem valor value40 , correspondendo a "4": "value40", no


MyArray.json . Os índices de matriz vinculados são contínuos e não vinculados ao índice
de chave de configuração. O associador de configuração não é capaz de vincular valores
nulos ou criar entradas nulas em objetos vinculados.

Provedor de Configuração personalizado


O aplicativo de exemplo demonstra como criar um provedor de configuração básico
que lê os pares chave-valor da configuração de um banco de dados usando Entity
Framework (EF).
O provedor tem as seguintes características:

O banco de dados EF na memória é usado para fins de demonstração. Para usar


um banco de dados que exija uma cadeia de conexão, implemente um
ConfigurationBuilder secundário para fornecer a cadeia de conexão de outro
provedor de configuração.
O provedor lê uma tabela de banco de dados na configuração na inicialização. O
provedor não consulta o banco de dados em uma base por chave.
O recarregamento na alteração não está implementado, portanto, a atualização do
banco de dados após a inicialização do aplicativo não tem efeito sobre a
configuração do aplicativo.

Defina uma entidade EFConfigurationValue para armazenar valores de configuração no


banco de dados.

Models/EFConfigurationValue.cs :

C#

public class EFConfigurationValue


{
public string Id { get; set; } = String.Empty;
public string Value { get; set; } = String.Empty;
}

Adicione um EFConfigurationContext para armazenar e acessar os valores configurados.

EFConfigurationProvider/EFConfigurationContext.cs :

C#

public class EFConfigurationContext : DbContext


{
public EFConfigurationContext(DbContextOptions<EFConfigurationContext>
options) : base(options)
{
}

public DbSet<EFConfigurationValue> Values => Set<EFConfigurationValue>


();
}

Crie uma classe que implementa IConfigurationSource.

EFConfigurationProvider/EFConfigurationSource.cs :
C#

public class EFConfigurationSource : IConfigurationSource


{
private readonly Action<DbContextOptionsBuilder> _optionsAction;

public EFConfigurationSource(Action<DbContextOptionsBuilder>
optionsAction) => _optionsAction = optionsAction;

public IConfigurationProvider Build(IConfigurationBuilder builder) =>


new EFConfigurationProvider(_optionsAction);
}

Crie o provedor de configuração personalizado através da herança de


ConfigurationProvider. O provedor de configuração inicializa o banco de dados quando
ele está vazio. Como as chaves de configuração não diferenciam maiúsculas de
minúsculas, o dicionário usado para inicializar o banco de dados é criado com o
comparador que não diferencia maiúsculas de minúsculas
(StringComparer.OrdinalIgnoreCase).

EFConfigurationProvider/EFConfigurationProvider.cs :

C#

public class EFConfigurationProvider : ConfigurationProvider


{
public EFConfigurationProvider(Action<DbContextOptionsBuilder>
optionsAction)
{
OptionsAction = optionsAction;
}

Action<DbContextOptionsBuilder> OptionsAction { get; }

public override void Load()


{
var builder = new DbContextOptionsBuilder<EFConfigurationContext>();

OptionsAction(builder);

using (var dbContext = new EFConfigurationContext(builder.Options))


{
if (dbContext == null || dbContext.Values == null)
{
throw new Exception("Null DB context");
}
dbContext.Database.EnsureCreated();

Data = !dbContext.Values.Any()
? CreateAndSaveDefaultValues(dbContext)
: dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
}
}

private static IDictionary<string, string> CreateAndSaveDefaultValues(


EFConfigurationContext dbContext)
{
// Quotes (c)2005 Universal Pictures: Serenity
// https://www.uphe.com/movies/serenity-2005
var configValues =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "quote1", "I aim to misbehave." },
{ "quote2", "I swallowed a bug." },
{ "quote3", "You can't stop the signal, Mal." }
};

if (dbContext == null || dbContext.Values == null)


{
throw new Exception("Null DB context");
}

dbContext.Values.AddRange(configValues
.Select(kvp => new EFConfigurationValue
{
Id = kvp.Key,
Value = kvp.Value
})
.ToArray());

dbContext.SaveChanges();

return configValues;
}
}

Um método de extensão AddEFConfiguration permite adicionar a fonte de configuração


a um ConfigurationBuilder .

Extensions/EntityFrameworkExtensions.cs :

C#

public static class EntityFrameworkExtensions


{
public static IConfigurationBuilder AddEFConfiguration(
this IConfigurationBuilder builder,
Action<DbContextOptionsBuilder> optionsAction)
{
return builder.Add(new EFConfigurationSource(optionsAction));
}
}
O código a seguir mostra como usar o EFConfigurationProvider personalizado no
Program.cs :

C#

//using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEFConfiguration(
opt => opt.UseInMemoryDatabase("InMemoryDb"));

var app = builder.Build();

app.Run();

Configuração de acesso com injeção de


dependência (DI)
A configuração pode ser injetada em serviços usando a injeção de dependência (DI)
resolvendo o serviço IConfiguration:

C#

public class Service


{
private readonly IConfiguration _config;

public Service(IConfiguration config) =>


_config = config;

public void DoSomething()


{
var configSettingValue = _config["ConfigSetting"];

// ...
}
}

Para obter informações sobre como acessar valores usando IConfiguration , confira
GetValue e GetSection, GetChildren e Exists neste artigo.

Acessar configuração no Razor Pages


O código a seguir exibe os dados de configuração em um Razor Page:
CSHTML

@page
@model Test5Model
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

No código a seguir, MyOptions é adicionada ao contêiner de serviço com Configure e


associada à configuração:

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

A marcação a seguir usa a diretiva @injectRazor para resolver e exibir os valores de


opções:

CSHTML

@page
@model SampleApp.Pages.Test3Model
@using Microsoft.Extensions.Options
@using SampleApp.Models
@inject IOptions<MyOptions> optionsAccessor

<p><b>Option1:</b> @optionsAccessor.Value.Option1</p>
<p><b>Option2:</b> @optionsAccessor.Value.Option2</p>

Acessar a configuração em um arquivo de


visualização MVC
O código a seguir exibe os dados de configuração em uma visualização MVC:

CSHTML
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

Acessar a configuração no Program.cs


O código a seguir acessa a configuração no arquivo Program.cs .

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

var key1 = app.Configuration.GetValue<int>("KeyOne");


var key2 = app.Configuration.GetValue<bool>("KeyTwo");

app.Logger.LogInformation($"KeyOne = {key1}");
app.Logger.LogInformation($"KeyTwo = {key2}");

app.Run();

Configurar opções com um delegado


As opções configuradas em um delegado substituem os valores definidos nos
provedores de configuração.

No código a seguir, um serviço IConfigureOptions<TOptions> é adicionado ao


contêiner de serviço. Ele usa um delegado para configurar valores para MyOptions :

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(myOptions =>
{
myOptions.Option1 = "Value configured in delegate";
myOptions.Option2 = 500;
});

var app = builder.Build();


O código a seguir exibe os valores das opções:

[!code-
csharp[~/fundamentals/configuration/options/samples/6.x/OptionsSample/Pages/Test2.
cshtml.cs?name=snippet)]

No exemplo anterior, os valores de Option1 e Option2 são especificados em


appsettings.json e substituídos pelo delegado configurado.

Configuração do host versus do aplicativo


Antes do aplicativo ser configurado e iniciado, um host é configurado e iniciado. O host
é responsável pelo gerenciamento de tempo de vida e pela inicialização do aplicativo. O
aplicativo e o host são configurados usando os provedores de configuração descritos
neste tópico. Os pares chave-valor de configuração do host também estão incluídos na
configuração do aplicativo. Para obter mais informações sobre como os provedores de
configuração são usados quando o host é compilado e como as fontes de configuração
afetam a configuração do host, confira Visão geral dos fundamentos do ASP.NET Core.

Configuração de host padrão


Para obter detalhes sobre a configuração padrão ao usar o host da Web, confira a
versão do ASP.NET Core 2.2 deste tópico.

A configuração do host é fornecida de:


Variáveis ​de ambiente prefixadas com DOTNET_ (por exemplo,
DOTNET_ENVIRONMENT ) usando o provedor de configuração de variáveis ​de

ambiente. O prefixo ( DOTNET_ ) é removido durante o carregamento dos pares


chave-valor de configuração.
Argumentos de linha de comando usando o Provedor de configuração de linha
de comando.
A configuração padrão do host da Web foi estabelecida
( ConfigureWebHostDefaults ):
O Kestrel é usado como o servidor Web e configurado usando provedores de
configuração do aplicativo.
Adicione o middleware de filtragem de host.
Adicione o middleware de cabeçalhos encaminhados se a variável de ambiente
ASPNETCORE_FORWARDEDHEADERS_ENABLED for definida para true .
Habilite a integração de IIS.
Outras configurações
Este tópico refere-se apenas à configuração do aplicativo. Outros aspectos da execução
e hospedagem de aplicativos ASP.NET Core são configurados usando arquivos de
configuração não abordados neste tópico:

launch.json / launchSettings.json são arquivos de configuração de ferramentas

para o ambiente de desenvolvimento, descritos:


em Usar vários ambientes no ASP.NET Core.
Em todo o conjunto de documentação em que os arquivos são usados para
configurar aplicativos ASP.NET Core para cenários de desenvolvimento.
web.config é um arquivo de configuração do servidor, descrito nos tópicos a

seguir:
Hospedar o ASP.NET Core no Windows com o IIS
Módulo do ASP.NET Core(ANCM) para IIS

As variáveis de ambiente definidas em launchSettings.json substituem as definidas no


ambiente do sistema.

Para obter mais informações sobre como migrar a configuração do aplicativo de versões
anteriores do ASP.NET, confira Migrar do ASP.NET para o ASP.NET Core.

Adicionar configuração de um assembly


externo
Uma implementação IHostingStartup permite adicionar melhorias a um aplicativo
durante a inicialização de um assembly externo fora da classe Startup do aplicativo.
Para obter mais informações, confira Usar assemblies de inicialização de hospedagem
no ASP.NET Core.

Recursos adicionais
Código fonte de configuração
Código-fonte do WebApplicationBuilder
Exibir ou baixar código de exemplo (como baixar)
Padrão de opções no ASP.NET Core
Configuração do ASP.NET Core Blazor
Padrão de opções no ASP.NET Core
Artigo • 10/01/2023 • 21 minutos para o fim da leitura

Por Kirk Larkin e Rick Anderson .

O padrão de opções usa classes para fornecer acesso fortemente tipado a grupos de
configurações relacionadas. Quando as definições de configuração são isoladas por
cenário em classes separadas, o aplicativo segue dois princípios importantes de
engenharia de software:

Encapsulamento:
As classes que dependem das definições de configuração dependem apenas
das definições de configuração que elas usam.
Separação de preocupações:
As configurações para diferentes partes do aplicativo não são dependentes ou
acopladas umas às outras.

As opções também fornecem um mecanismo para validar os dados da configuração.


Para obter mais configurações, consulte a seção Validação de opções.

Este artigo fornece informações sobre o padrão de opções em ASP.NET Core. Para obter
informações sobre como usar o padrão de opções em aplicativos de console, consulte
Padrão de opções no .NET.

Associar configuração hierárquica


A maneira preferencial de ler os valores de configuração relacionados é usando o
padrão de opções. Por exemplo, para ler os seguintes valores de configuração:

JSON

"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}

Crie a seguinte classe PositionOptions :

C#

public class PositionOptions


{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}

Uma classe de opções:

Deve ser não abstrata e com um construtor público sem parâmetros.


Todas as propriedades públicas de leitura e gravação do tipo são vinculadas.
Os campos não são vinculados. No código anterior, Position não está vinculado.
O campo Position é usado para que a cadeia de caracteres "Position" não
precise ser codificada no aplicativo ao associar a classe a um provedor de
configuração.

O seguinte código:

Chama ConfigurationBinder.Bind para associar a classe PositionOptions à seção


Position .

Exibe os dados de configuração de Position .

C#

public class Test22Model : PageModel


{
private readonly IConfiguration Configuration;

public Test22Model(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var positionOptions = new PositionOptions();

Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

return Content($"Title: {positionOptions.Title} \n" +


$"Name: {positionOptions.Name}");
}
}

No código anterior, por padrão, as alterações no arquivo de configuração JSON após a


inicialização do aplicativo são lidas.

ConfigurationBinder.Get<T> associa e retorna o tipo especificado.


ConfigurationBinder.Get<T> pode ser mais conveniente do que usar
ConfigurationBinder.Bind . O código a seguir mostra como usar

ConfigurationBinder.Get<T> com a classe PositionOptions :

C#

public class Test21Model : PageModel


{
private readonly IConfiguration Configuration;
public PositionOptions? positionOptions { get; private set; }

public Test21Model(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
positionOptions = Configuration.GetSection(PositionOptions.Position)
.Get<PositionOptions>
();

return Content($"Title: {positionOptions.Title} \n" +


$"Name: {positionOptions.Name}");
}
}

No código anterior, por padrão, as alterações no arquivo de configuração JSON após a


inicialização do aplicativo são lidas.

Uma abordagem alternativa ao usar o padrão de opções é associar a seção Position e


adicioná-la ao contêiner do serviço de injeção de dependência. No código a seguir,
PositionOptions é adicionada ao contêiner de serviço com Configure e associada à

configuração:

C#

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

Usando o código anterior, o código a seguir lê as opções de posição:


C#

public class Test2Model : PageModel


{
private readonly PositionOptions _options;

public Test2Model(IOptions<PositionOptions> options)


{
_options = options.Value;
}

public ContentResult OnGet()


{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}

No código anterior, as alterações no arquivo de configuração JSON após a inicialização


do aplicativo não são lidas. Para ler as alterações após a inicialização do aplicativo, use
IOptionsSnapshot.

Interfaces de opções
IOptions<TOptions>:

Não é compatível com:


Leitura de dados de configuração após o início do aplicativo.
Opções nomeadas
É registrado como singleton e pode ser injetado em qualquer tempo de vida do
serviço.

IOptionsSnapshot<TOptions>:

É útil em cenários em que as opções devem ser recomputadas em cada solicitação.


Para obter mais informações, consulte Usar IOptionsSnapshot para ler dados
atualizados.
É registrado como Escopo e, portanto, não pode ser injetado em um serviço
Singleton.
Permite opções nomeadas

IOptionsMonitor<TOptions>:

O é usado para recuperar as opções e gerenciar notificações de opções para


instâncias de TOptions .
É registrado como singleton e pode ser injetado em qualquer tempo de vida do
serviço.
Oferece suporte a:
Notificações de alteração
opções nomeadas
Configuração recarregável
Invalidação seletiva de opções (IOptionsMonitorCache<TOptions>)

Cenários pós-configuração permitem definir ou alterar opções depois que todas as


IConfigureOptions<TOptions> configurações ocorrem.

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.

Usar IOptionsSnapshot para ler dados


atualizados
Usando IOptionsSnapshot<TOptions>:

As opções são calculadas uma vez por solicitação, quando acessadas e


armazenadas em cache durante o tempo de vida da solicitação.
Pode incorrer em uma penalidade de desempenho significativa porque é um
serviço com escopo e é recomputado por solicitação. Para obter mais informações,
consulte este problema do GitHub e Melhorar o desempenho da associação de
configuração .
As alterações na configuração são lidas depois que o aplicativo é iniciado ao usar
provedores de configuração que permitem a leitura de valores de configuração
atualizados.

A diferença entre IOptionsMonitor e IOptionsSnapshot é que:


IOptionsMonitor é um serviço Singleton que recupera valores de opção atuais a

qualquer momento, o que é especialmente útil em dependências singleton.


IOptionsSnapshot é um serviço com escopo e fornece um instantâneo das opções

no momento em que o IOptionsSnapshot<T> objeto é construído. Os instantâneos


de opções são projetados para uso com dependências transitórias e com escopo.

O código a seguir usa IOptionsSnapshot<TOptions>.

C#

public class TestSnapModel : PageModel


{
private readonly MyOptions _snapshotOptions;

public TestSnapModel(IOptionsSnapshot<MyOptions>
snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}

public ContentResult OnGet()


{
return Content($"Option1: {_snapshotOptions.Option1} \n" +
$"Option2: {_snapshotOptions.Option2}");
}
}

O código a seguir registra uma instância de configuração que MyOptions associa a:

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

No código anterior, as alterações no JSarquivo de configuração ON após o início do


aplicativo são lidas.

IOptionsMonitor
O código a seguir registra uma instância de configuração a que MyOptions se associa.

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

O exemplo a seguir usa IOptionsMonitor<TOptions>:

C#

public class TestMonitorModel : PageModel


{
private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )


{
_optionsDelegate = optionsDelegate;
}

public ContentResult OnGet()


{
return Content($"Option1: {_optionsDelegate.CurrentValue.Option1}
\n" +
$"Option2: {_optionsDelegate.CurrentValue.Option2}");
}
}

No código anterior, por padrão, as alterações no arquivo de configuração JSON após a


inicialização do aplicativo são lidas.

Compatibilidade de opções nomeadas usando


IConfigureNamedOptions
Opções nomeadas:

São úteis quando várias seções de configuração se associam às mesmas


propriedades.
Diferenciam maiúsculas de minúsculas.
Considere o seguinte arquivo appsettings.json :

JSON

{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}

Em vez de criar duas classes para associar TopItem:Month e TopItem:Year , a seguinte


classe é usada para cada seção:

C#

public class TopItemSettings


{
public const string Month = "Month";
public const string Year = "Year";

public string Name { get; set; } = string.Empty;


public string Model { get; set; } = string.Empty;
}

O código a seguir configura as opções nomeadas:

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));

var app = builder.Build();

O código a seguir mostra as opções nomeadas:


C#

public class TestNOModel : PageModel


{
private readonly TopItemSettings _monthTopItem;
private readonly TopItemSettings _yearTopItem;

public TestNOModel(IOptionsSnapshot<TopItemSettings>
namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}

public ContentResult OnGet()


{
return Content($"Month:Name {_monthTopItem.Name} \n" +
$"Month:Model {_monthTopItem.Model} \n\n" +
$"Year:Name {_yearTopItem.Name} \n" +
$"Year:Model {_yearTopItem.Model} \n" );
}
}

Todas as opções são instâncias nomeadas. As instâncias IConfigureOptions<TOptions>


existentes são tratadas como sendo direcionadas à instância Options.DefaultName , que é
string.Empty . IConfigureNamedOptions<TOptions> também implementa

IConfigureOptions<TOptions>. A implementação padrão de


IOptionsFactory<TOptions> tem lógica para usar cada um de forma adequada. A opção
nomeada null é usada para direcionar todas as instâncias nomeadas, em vez de uma
instância nomeada específica. ConfigureAll e PostConfigureAll usam essa convenção.

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 .

OptionsBuilder é usado na seção Validação de opções.

Confira Usar AddOptions para configurar o repositório personalizado para obter


informações sobre como adicionar um repositório personalizado.
Usar os serviços de injeção de dependência
para configurar as opções
Os serviços podem ser acessados de injeção de dependência ao configurar as opções de
duas maneiras:

Passe um delegado de configuração para Configure em


OptionsBuilder<TOptions>. OptionsBuilder<TOptions> fornece sobrecargas de
Configure que permitem o uso de até cinco serviços para configurar opções:

C#

builder.Services.AddOptions<MyOptions>("optionalName")
.Configure<Service1, Service2, Service3, Service4, Service5>(
(o, s, s2, s3, s4, s5) =>
o.Property = DoSomethingWith(s, s2, s3, s4, s5));

Criar um tipo que implementa IConfigureOptions<TOptions> ou


IConfigureNamedOptions<TOptions> e registra o tipo como um serviço.

É recomendável passar um delegado de configuração para Configure, pois a criação de


um serviço é mais complexa. A criação de um tipo é equivalente ao que a estrutura faz
ao chamar Configure. Chamar Configure registra um genérico
IConfigureNamedOptions<TOptions>transitório , que tem um construtor que aceita os
tipos de serviço genéricos especificados.

Validação de opções
A validação de opções permite que valores de opção sejam validados.

Considere o seguinte arquivo appsettings.json :

JSON

{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}

A seguinte classe é usada para associar à "MyConfig" seção de configuração e aplica


algumas DataAnnotations regras:
C#

public class MyConfigOptions


{
public const string MyConfig = "MyConfig";

[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public string Key1 { get; set; }
[Range(0, 1000,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int Key2 { get; set; }
public int Key3 { get; set; }
}

O seguinte código:

Chama AddOptions para obter um OptionsBuilder<TOptions> que se associa à


MyConfigOptions classe .

Chamadas ValidateDataAnnotations para habilitar a validação usando


DataAnnotations .

C#

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()

.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();

var app = builder.Build();

O método de extensão ValidateDataAnnotations é definido no pacote


Microsoft.Extensions.Options.DataAnnotations do NuGet. Para aplicativos Web que
usam o Microsoft.NET.Sdk.Web SDK, esse pacote é referenciado implicitamente da
estrutura compartilhada.

O código a seguir exibe os valores de configuração ou os erros de validação:

C#

public class HomeController : Controller


{
private readonly ILogger<HomeController> _logger;
private readonly IOptions<MyConfigOptions> _config;

public HomeController(IOptions<MyConfigOptions> config,


ILogger<HomeController> logger)
{
_config = config;
_logger = logger;

try
{
var configValue = _config.Value;

}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
_logger.LogError(failure);
}
}
}

public ContentResult Index()


{
string msg;
try
{
msg = $"Key1: {_config.Value.Key1} \n" +
$"Key2: {_config.Value.Key2} \n" +
$"Key3: {_config.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
return Content(optValEx.Message);
}
return Content(msg);
}

O código a seguir aplica uma regra de validação mais complexa usando um


representante:

C#

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()

.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Key2 != 0)
{
return config.Key3 > config.Key2;
}

return true;
}, "Key3 must be > than Key2."); // Failure message.

var app = builder.Build();

IValidateOptions<TOptions> e IValidatableObject

A classe a seguir implementa IValidateOptions<TOptions>:

C#

public class MyConfigValidation : IValidateOptions<MyConfigOptions>


{
public MyConfigOptions _config { get; private set; }

public MyConfigValidation(IConfiguration config)


{
_config = config.GetSection(MyConfigOptions.MyConfig)
.Get<MyConfigOptions>();
}

public ValidateOptionsResult Validate(string name, MyConfigOptions


options)
{
string? vor = null;
var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
var match = rx.Match(options.Key1!);

if (string.IsNullOrEmpty(match.Value))
{
vor = $"{options.Key1} doesn't match RegEx \n";
}

if ( options.Key2 < 0 || options.Key2 > 1000)


{
vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
}

if (_config.Key2 != default)
{
if(_config.Key3 <= _config.Key2)
{
vor += "Key3 must be > than Key2.";
}
}
if (vor != null)
{
return ValidateOptionsResult.Fail(vor);
}

return ValidateOptionsResult.Success;
}
}

IValidateOptions permite mover o código de validação de Program.cs e para uma


classe.

Usando o código anterior, a validação está habilitada em Program.cs com o seguinte


código:

C#

using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.Configure<MyConfigOptions>
(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));

builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();

var app = builder.Build();

A validação de opções também dá IValidatableObjectsuporte a . Para executar a


validação em nível de classe de uma classe dentro da própria classe:

Implemente a IValidatableObject interface e seu Validate método dentro da


classe .
Chame ValidateDataAnnotations em Program.cs .

ValidateOnStart

A validação de opções é executada na primeira vez que uma


IOptions<TOptions>implementação , IOptionsSnapshot<TOptions>ou
IOptionsMonitor<TOptions> é criada. Para executar a validação de opções
ansiosamente, quando o aplicativo for iniciado, chame ValidateOnStart em Program.cs :
C#

builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();

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:

C#

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()

.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});

O PostConfigure está disponível para pós-configurar opções nomeadas:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));

builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>


{
myOptions.Name = "post_configured_name_value";
myOptions.Model = "post_configured_model_value";
});

var app = builder.Build();


Use PostConfigureAll para pós-configurar todas as instâncias de configuração:

C#

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()

.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});

Opções de acesso em Program.cs


Para acessar IOptions<TOptions> ou IOptionsMonitor<TOptions> em Program.cs ,
chame GetRequiredService em WebApplication.Services:

C#

var app = builder.Build();

var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()


.CurrentValue.Option1;

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Usar vários ambientes no ASP.NET Core
Artigo • 28/11/2022 • 23 minutos para o fim da leitura

Por Rick Anderson e Kirk Larkin

O ASP.NET Core configura o comportamento do aplicativo com base no ambiente de


runtime usando uma variável de ambiente.

Ambientes
Para determinar o ambiente de runtime, ASP.NET Core leituras das seguintes variáveis
de ambiente:

1. DOTNET_ENVIRONMENT
2. ASPNETCORE_ENVIRONMENT quando o WebApplication.CreateBuilder método é
chamado. A chamada WebApplication.CreateBuilder padrão ASP.NET Core
modelos de aplicativo Web . O ASPNETCORE_ENVIRONMENT valor substitui
DOTNET_ENVIRONMENT .

IHostEnvironment.EnvironmentName pode ser definido como qualquer valor, mas os

seguintes valores são fornecidos pela estrutura:

Development: o arquivo launchSettings.json define ASPNETCORE_ENVIRONMENT para


Development o computador local.

Staging
Production: o padrão se DOTNET_ENVIRONMENT e ASPNETCORE_ENVIRONMENT não tiver
sido definido.

O seguinte código:

É semelhante ao código gerado pelos modelos de ASP.NET Core.


Habilita a Página de Exceção do Desenvolvedor quando ASPNETCORE_ENVIRONMENT
está definida como Development . Isso é feito automaticamente pelo
WebApplication.CreateBuilder método.
Chama UseExceptionHandler quando o valor é algo diferente de
ASPNETCORE_ENVIRONMENT Development .
Fornece uma IWebHostEnvironment instância na Environment propriedade de
WebApplication .

C#
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O Auxiliar de Marca de Ambiente usa o valor de IHostEnvironment.EnvironmentName


para incluir ou excluir marcação no elemento:

CSHTML

<environment include="Development">
<div>Environment is Development</div>
</environment>
<environment exclude="Development">
<div>Environment is NOT Development</div>
</environment>
<environment include="Staging,Development,Staging_2">
<div>Environment is: Staging, Development or Staging_2</div>
</environment>

A página Sobre do código de exemplo inclui a marcação anterior e exibe o valor de


IWebHostEnvironment.EnvironmentName .

No Windows e no macOS, variáveis de ambiente e valores não diferenciam maiúsculas


de minúsculas. Por padrão, as variáveis de ambiente e os valores do Linux diferenciam
maiúsculas de minúsculas.
Criar AmbientesSample
O código de exemplo usado neste artigo baseia-se em um Razor projeto de Páginas
chamado EnvironmentsSample.

Os seguintes comandos da CLI do .NET criam e executam um aplicativo Web chamado


EnvironmentsSample:

Bash

dotnet new webapp -o EnvironmentsSample


cd EnvironmentsSample
dotnet run --verbosity normal

Quando o aplicativo é executado, ele exibe uma saída semelhante à seguinte:

Bash

info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7152
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5105
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Path\To\EnvironmentsSample

Definir ambiente na linha de comando


Use o --environment sinalizador para definir o ambiente. Por exemplo:

CLI do .NET

dotnet run --environment Production

O comando anterior define o ambiente Production como e exibe uma saída semelhante
à seguinte na janela de comando:

Bash

info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7262
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5005
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Path\To\EnvironmentsSample

Desenvolvimento e launchSettings.json
O ambiente de desenvolvimento pode habilitar recursos que não devem ser expostos
em produção. Por exemplo, os modelos de projeto ASP.NET Core habilitam a Página de
Exceção do Desenvolvedor no ambiente de desenvolvimento. Devido ao custo de
desempenho, a validação de escopo e a validação de dependência só acontecem no
desenvolvimento.

O ambiente de desenvolvimento do computador local pode ser definido no arquivo


Properties\launchSettings.json do projeto. Valores de ambiente definidos em
launchSettings.json valores de substituição definidos no ambiente do sistema.

O arquivo launchSettings.json :

É usado apenas no computador de desenvolvimento local.


Não foi implantado.
Contém configurações de perfil.

O ON a seguir JSmostra o launchSettings.json arquivo de um projeto Web ASP.NET


Core chamado EnvironmentsSample criado com o Visual Studio ou dotnet new :

JSON

{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:59481",
"sslPort": 44308
}
},
"profiles": {
"EnvironmentsSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7152;http://localhost:5105",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

O ON anterior JScontém dois perfis:

EnvironmentsSample : o nome do perfil é o nome do projeto. Como o primeiro perfil

listado, esse perfil é usado por padrão. A "commandName" chave tem o valor
"Project" , portanto, o Kestrel servidor Web é iniciado.

IIS Express : a "commandName" chave tem o valor "IISExpress" , portanto,

IISExpress é o servidor Web.

Você pode definir o perfil de inicialização para o projeto ou qualquer outro perfil
incluído em launchSettings.json . Por exemplo, na imagem abaixo, selecionar o nome
do projeto inicia o Kestrel servidor Web.

O valor de commandName pode especificar o servidor Web a ser iniciado. commandName


pode ser qualquer um dos seguintes:

IISExpress : inicia IIS Express.

IIS : nenhum servidor Web foi iniciado. Espera-se que o IIS esteja disponível.
Project : inicializa Kestrel.

A guia Depuração/Geral das propriedades do projeto do Visual Studio 2022 fornece um


link de interface do usuário de perfis de inicialização do Open debug . Este link abre
uma caixa de diálogo Iniciar Perfis que permite editar as configurações de variável de
ambiente no launchSettings.json arquivo. Você também pode abrir a caixa de diálogo
Iniciar Perfis no menu Depurar selecionando <o nome> do projeto Propriedades de
Depuração. As alterações feitas nos perfis do projeto poderão não ter efeito até que o
servidor Web seja reiniciado. Kestrel deve ser reiniciado antes de detectar alterações
feitas em seu ambiente.

O arquivo a seguir launchSettings.json contém vários perfis:

JSON

{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:59481",
"sslPort": 44308
}
},
"profiles": {
"EnvironmentsSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7152;http://localhost:5105",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"EnvironmentsSample-Staging": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7152;http://localhost:5105",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging",
"ASPNETCORE_DETAILEDERRORS": "1",
"ASPNETCORE_SHUTDOWNTIMEOUTSECONDS": "3"
}
},
"EnvironmentsSample-Production": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7152;http://localhost:5105",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

Os perfis podem ser selecionados:

Na interface do usuário do Visual Studio.

Usando o dotnet run comando da CLI com a opção --launch-profile definida


como o nome do perfil. Essa abordagem só dá suporte a Kestrel perfis.

CLI do .NET

dotnet run --launch-profile "SampleApp"

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


.vscode/launch.json arquivo. O exemplo a seguir define várias variáveis de ambiente
para valores de configuração do host:

JSON

{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
// Configuration ommitted for brevity.
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "https://localhost:5001",
"ASPNETCORE_DETAILEDERRORS": "1",
"ASPNETCORE_SHUTDOWNTIMEOUTSECONDS": "3"
},
// Configuration ommitted for brevity.

O .vscode/launch.json arquivo é usado apenas por Visual Studio Code.

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.
Registro em log e monitoramento de produção habilitados. Por exemplo, usando o
Application Insights.

Definir o ambiente definindo uma variável de


ambiente
Geralmente, é útil definir um ambiente específico para teste com uma variável de
ambiente ou configuração de plataforma. 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.

Quando o host é criado, a última configuração de ambiente lida pelo aplicativo


determina o ambiente do aplicativo. O ambiente do aplicativo não pode ser alterado
enquanto o aplicativo está em execução.

A página Sobre do código de exemplo exibe o valor de


IWebHostEnvironment.EnvironmentName .

Serviço de aplicativo do Azure


Production é o valor padrão se DOTNET_ENVIRONMENT e ASPNETCORE_ENVIRONMENT não tiver
sido definido. Os aplicativos implantados no Azure são Production por padrão.

Para definir o ambiente em um aplicativo Serviço de Aplicativo do Azure usando o


portal:

1. Selecione o aplicativo na página Serviços de Aplicativo .


2. No grupo Configurações , selecione Configuração.
3. Na guia Configurações do aplicativo , selecione Nova configuração do aplicativo.
4. Na janela Adicionar/Editar configuração do aplicativo , forneça
ASPNETCORE_ENVIRONMENT o Nome. Para Valor, forneça o ambiente (por exemplo,

Staging ).
5. Selecione a caixa de seleção de configuração de slot de implantação se desejar
que a configuração de ambiente permaneça com o slot atual quando os slots de
implantação forem trocados. Para obter mais informações, consulte Configurar
ambientes de preparo em Serviço de Aplicativo do Azure na documentação do
Azure.
6. Selecione OK para fechar a caixa de diálogo Adicionar/Editar configuração do
aplicativo .
7. Selecione Salvar na parte superior da página Configuração .

Serviço de Aplicativo do Azure reinicia automaticamente o aplicativo depois que uma


configuração de aplicativo é adicionada, alterada ou excluída no portal do Azure.

Windows – Definir variável de ambiente para um


processo
Valores de ambiente em launchSettings.json valores de substituição definidos no
ambiente do sistema.

Para definir a ASPNETCORE_ENVIRONMENT sessão atual quando o aplicativo for iniciado


usando a execução dotnet, use os seguintes comandos em um prompt de comando ou
no PowerShell:

Console

set ASPNETCORE_ENVIRONMENT=Staging
dotnet run --no-launch-profile

PowerShell

$Env:ASPNETCORE_ENVIRONMENT = "Staging"
dotnet run --no-launch-profile

Windows – Definir variável de ambiente globalmente


Os comandos anteriores são definidos ASPNETCORE_ENVIRONMENT apenas para processos
iniciados a partir dessa janela de comando.

Para definir o valor globalmente no Windows, use uma das seguintes abordagens:

Abra asconfigurações do sistemaPainel de Controle>System> Advanced e


adicione ou edite o ASPNETCORE_ENVIRONMENT valor:
Abra um prompt de comando administrativo e use o comando setx ou abra um
prompt de comando administrativo do PowerShell e use
[Environment]::SetEnvironmentVariable :

Console

setx ASPNETCORE_ENVIRONMENT Staging /M

A /M opção define a variável de ambiente no nível do sistema. Se o comutador


/M não for usado, a variável de ambiente será definida para a conta de usuário.

PowerShell

[Environment]::SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT",
"Staging", "Machine")

A Machine opção define 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. Valores de ambiente em launchSettings.json valores de substituição
definidos no ambiente do sistema.

Windows – Usar web.config


Para definir a ASPNETCORE_ENVIRONMENT variável de ambiente com web.config , consulte a
seção Definir variáveis de ambiente de web.config arquivo.

Windows – Implantações do IIS


Inclua a <EnvironmentName> propriedade no perfil de publicação (.pubxml) ou no arquivo
de projeto. Esta abordagem define o ambiente no arquivo web.config quando o projeto
é publicado:

XML

<PropertyGroup>
<EnvironmentName>Development</EnvironmentName>
</PropertyGroup>

Para definir a variável de ASPNETCORE_ENVIRONMENT ambiente para um aplicativo em


execução em um Pool de Aplicativos isolado (com suporte no IIS 10.0 ou posterior),
consulte a seção de comandoAppCmd.exe de Environment Variables
<environmentVariables>. Quando a variável de ambiente ASPNETCORE_ENVIRONMENT é
definida para um pool de aplicativos, seu valor substitui uma configuração no nível do
sistema.

Ao hospedar um aplicativo no IIS e adicionar ou alterar a ASPNETCORE_ENVIRONMENT


variável de ambiente, use uma das seguintes abordagens para ter o novo valor captado
pelos 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:

Bash

ASPNETCORE_ENVIRONMENT=Staging dotnet run

Como alternativa, defina o ambiente com export antes de executar o aplicativo:

Bash

export ASPNETCORE_ENVIRONMENT=Staging

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:

Bash
export ASPNETCORE_ENVIRONMENT=Staging

Linux
Para distribuições do Linux, use o export comando em um prompt de comando para
configurações de variáveis baseadas em sessão e o arquivo bash_profile para
configurações de ambiente no nível do computador.

Definir o ambiente no código


Para definir o ambiente no código, use WebApplicationOptions.EnvironmentName ao
criar WebApplicationBuilder, conforme mostrado no exemplo a seguir:

C#

var builder = WebApplication.CreateBuilder(new WebApplicationOptions


{
EnvironmentName = Environments.Staging
});

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Para saber mais, confira Host Genérico .NET no ASP.NET Core.


Configuração por ambiente
Para carregar a configuração por ambiente, consulte Configuração em ASP.NET Core.

Configurar serviços e middleware por ambiente


Use WebApplicationBuilder.Environment ou WebApplication.Environment adicione
condicionalmente serviços ou middleware dependendo do ambiente atual. O modelo de
projeto inclui um exemplo de código que adiciona middleware somente quando o
ambiente atual não é Desenvolvimento:

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O código realçado verifica o ambiente atual durante a criação do pipeline de solicitação.


Para verificar o ambiente atual ao configurar serviços, use builder.Environment em vez
de app.Environment .

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Inicialização de aplicativo no ASP.NET Core
Configuração no ASP.NET Core
Ambientes Blazor do ASP.NET Core
Como fazer registro em log no .NET
Core e no ASP.NET Core
Artigo • 04/01/2023 • 65 minutos para o fim da leitura

Por Kirk Larkin , Juergen Gutsch e Rick Anderson

Este tópico descreve o registro em log no .NET no que diz respeito a aplicativos
ASP.NET Core. Para obter informações detalhadas sobre o registro em log no .NET,
consulte Registro em log no .NET. Para saber mais sobre o registro em log em
aplicativos Blazor, confira Registro em log no ASP.NET CoreBlazor.

Provedores de log
Os provedores de log armazenam logs, exceto pelo provedor Console que exibe logs.
Por exemplo, o provedor do Azure Application Insights armazena logs no Azure
Application Insights. Vários provedores podem ser habilitados.

Os modelos padrão de aplicativo Web ASP.NET Core:

Usam o Host Genérico.


Chamam WebApplication.CreateBuilder, que adiciona os seguintes provedores de
registro em log:
Console
Depurar
EventSource
EventLog: somente Windows

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O código anterior mostra o arquivo Program.cs criado com os modelos de aplicativo


Web do ASP.NET Core. As próximas seções fornecem exemplos baseados nos modelos
de aplicativo Web do ASP.NET Core, que usam o Host Genérico.

O seguinte código substitui o conjunto padrão de provedores de registro em log


adicionados por WebApplication.CreateBuilder :

C#

var builder = WebApplication.CreateBuilder(args);


builder.Logging.ClearProviders();
builder.Logging.AddConsole();

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Como alternativa, o código de registro em log anterior pode ser escrito da seguinte
maneira:

C#

var builder = WebApplication.CreateBuilder();


builder.Host.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
});

Para provedores adicionais, consulte:

Provedores de log internos


Provedores de log de terceiros.

Criar logs
Para criar logs, use um objeto ILogger<TCategoryName> de DI (injeção de
dependência).

O exemplo a seguir:

Cria um agente, ILogger<AboutModel> , que usa uma categoria de log do nome


totalmente qualificado do tipo AboutModel . A categoria do log é uma cadeia de
caracteres associada a cada log.
Chama LogInformation para registrar em log no nível Information. O nível de log
indica a gravidade do evento registrado.

C#

public class AboutModel : PageModel


{
private readonly ILogger _logger;

public AboutModel(ILogger<AboutModel> logger)


{
_logger = logger;
}

public void OnGet()


{
_logger.LogInformation("About page visited at {DT}",
DateTime.UtcNow.ToLongTimeString());
}
}

Níveis e categorias serão explicados com mais detalhes posteriormente neste artigo.

Para obter informações sobre Blazor, consulte Registro em log de Blazor no ASP.NET
Core.
Configurar o registro em log
A configuração de registro em log normalmente é fornecida pela seção Logging dos
arquivos appsettings.{ENVIRONMENT}.json , em que o espaço reservado {ENVIRONMENT} é
o ambiente. O seguinte arquivo appsettings.Development.json é gerado pelos modelos
de aplicativo Web do ASP.NET Core:

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

No JSON anterior:

As categorias "Default" e "Microsoft.AspNetCore" são especificadas.


A categoria "Microsoft.AspNetCore" se aplica a todas as categorias que começam
com "Microsoft.AspNetCore" . Por exemplo, essa configuração se aplica à categoria
"Microsoft.AspNetCore.Routing.EndpointMiddleware" .

A categoria "Microsoft.AspNetCore" faz registra no nível de log Warning e


superiores.
Não é especificado um provedor de log específico, portanto LogLevel se aplica a
todos os provedores de log habilitados, exceto por EventLog do Windows.

A propriedade Logging pode ter LogLevel e as propriedades do provedor de logs. A


LogLevel especifica o nível mínimo de log nas categorias selecionadas. No JSON
anterior, os níveis de log Information e Warning são especificados. LogLevel indica a
severidade do log e varia de 0 a 6:

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 e None =

6.

Quando um LogLevel é especificado, o registro em log é habilitado para mensagens no


nível especificado e superior. No JSON anterior, a categoria Default é registrada para
Information e superior. Por exemplo, mensagens Information , Warning , Error e
Critical são registradas. Se LogLevel não for especificado, o registro em log usará o

nível Information como padrão. Para obter mais informações, confira Níveis de log.
Uma propriedade de provedor pode especificar uma propriedade LogLevel . LogLevel
em um provedor especifica os níveis de log desse provedor e substitui as configurações
de log que não são do provedor. Considere o seguinte arquivo appsettings.json :

JSON

{
"Logging": {
"LogLevel": { // All providers, LogLevel applies to all the enabled
providers.
"Default": "Error", // Default logging, Error and higher.
"Microsoft": "Warning" // All Microsoft* categories, Warning and
higher.
},
"Debug": { // Debug provider.
"LogLevel": {
"Default": "Information", // Overrides preceding LogLevel:Default
setting.
"Microsoft.Hosting": "Trace" // Debug:Microsoft.Hosting category.
}
},
"EventSource": { // EventSource provider
"LogLevel": {
"Default": "Warning" // All categories of EventSource provider.
}
}
}
}

Configurações em Logging.{PROVIDER NAME}.LogLevel substituem configurações em


Logging.LogLevel , em que o espaço reservado {PROVIDER NAME} é o nome do provedor.

No JSON anterior, o nível de log padrão do provedor Debug é definido como


Information :

Logging:Debug:LogLevel:Default:Information

A configuração anterior especifica o nível de log Information para todas as categorias


de Logging:Debug: , exceto por Microsoft.Hosting . Quando uma categoria específica é
listada, ela substitui a categoria padrão. No JSON anterior, as categorias
"Microsoft.Hosting" e "Default" de Logging:Debug:LogLevel substituem as

configurações em Logging:LogLevel .

O nível mínimo de log pode ser especificado para:

Provedores específicos: por exemplo,


Logging:EventSource:LogLevel:Default:Information
Categorias específicas: por exemplo, Logging:LogLevel:Microsoft:Warning
Todos os provedores e todas as categorias: Logging:LogLevel:Default:Warning

Todos os logs abaixo do nível mínimo não são:

Passados para o provedor.


Registrados nem exibidos.

Para suprimir todos os logs, especifique LogLevel.None. LogLevel.None tem o valor 6,


que é superior a LogLevel.Critical (5).

Se um provedor oferecer suporte a escopos de log, IncludeScopes indicará se eles estão


habilitados. Para obter mais informações, confira Escopos de log.

O seguinte arquivo appsettings.json contém todos os provedores habilitados por


padrão:

JSON

{
"Logging": {
"LogLevel": { // No provider, LogLevel applies to all the enabled
providers.
"Default": "Error",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Warning"
},
"Debug": { // Debug provider.
"LogLevel": {
"Default": "Information" // Overrides preceding LogLevel:Default
setting.
}
},
"Console": {
"IncludeScopes": true,
"LogLevel": {
"Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
"Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
"Microsoft.AspNetCore.Mvc.Razor": "Error",
"Default": "Information"
}
},
"EventSource": {
"LogLevel": {
"Microsoft": "Information"
}
},
"EventLog": {
"LogLevel": {
"Microsoft": "Information"
}
},
"AzureAppServicesFile": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Warning"
}
},
"AzureAppServicesBlob": {
"IncludeScopes": true,
"LogLevel": {
"Microsoft": "Information"
}
},
"ApplicationInsights": {
"LogLevel": {
"Default": "Information"
}
}
}
}

Na amostra anterior:

As categorias e os níveis não são valores sugeridos. O exemplo é fornecido para


mostrar todos os provedores padrão.
Configurações em Logging.{PROVIDER NAME}.LogLevel substituem configurações
em Logging.LogLevel , em que o espaço reservado {PROVIDER NAME} é o nome do
provedor. Por exemplo, o nível em Debug.LogLevel.Default substitui o nível em
LogLevel.Default .

Cada alias de provedor padrão é usado. Cada provedor define um alias que pode
ser usado na configuração no lugar do nome de tipo totalmente qualificado. Os
aliases dos provedores internos são:
Console
Debug

EventSource
EventLog

AzureAppServicesFile

AzureAppServicesBlob
ApplicationInsights

Logon Program.cs
O seguinte exemplo chama Builder.WebApplication.Loggerem Program.cs e registra
mensagens informativas:
C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();
app.Logger.LogInformation("Adding Routes");
app.MapGet("/", () => "Hello World!");
app.Logger.LogInformation("Starting the app");
app.Run();

O seguinte exemplo chama AddConsole em Program.cs e registra o ponto de


extremidade /Test :

C#

var builder = WebApplication.CreateBuilder(args);

var logger = LoggerFactory.Create(config =>


{
config.AddConsole();
}).CreateLogger("Program");

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/Test", async context =>


{
logger.LogInformation("Testing logging in Program.cs");
await context.Response.WriteAsync("Testing");
});

app.Run();

O seguinte exemplo chama AddSimpleConsole em Program.cs , desabilita a saída de cor


e registra o ponto de extremidade /Test :

C#

using Microsoft.Extensions.Logging.Console;

var builder = WebApplication.CreateBuilder(args);

using var loggerFactory = LoggerFactory.Create(builder =>


{
builder.AddSimpleConsole(i => i.ColorBehavior =
LoggerColorBehavior.Disabled);
});

var logger = loggerFactory.CreateLogger<Program>();


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/Test", async context =>


{
logger.LogInformation("Testing logging in Program.cs");
await context.Response.WriteAsync("Testing");
});

app.Run();

Definir o nível de log por linha de comando,


variáveis de ambiente e outras configurações
O nível de log pode ser definido por qualquer um dos provedores de configuração.

O separador : não funciona com chaves hierárquicas de variáveis de ambiente em


todas as plataformas. __ , o sublinhado duplo, tem:

Suporte em todas as plataformas. Por exemplo, o separador : não tem suporte


pelo Bash , mas pelo __ tem.
Substituição automática por um :

Os comandos seguintes:

Defina a chave de ambiente Logging:LogLevel:Microsoft como um valor de


Information no Windows.
Teste as configurações ao usar um aplicativo criado com os modelos de aplicativo
Web do ASP.NET Core. O comando dotnet run precisa ser executado no diretório
do projeto após usar set .

CLI do .NET

set Logging__LogLevel__Microsoft=Information
dotnet run

As configurações de ambiente anteriores:

São definidas apenas em processos iniciados na janela de comando em que foram


definidos.
Não são lidos por navegadores iniciados com o Visual Studio.
O comando setx a seguir também define a chave e o valor do ambiente no Windows.
Diferente de set , as configurações setx são persistentes. A opção /M define a variável
no ambiente do sistema. Se /M não for usado, uma variável de ambiente do usuário
será definida.

Console

setx Logging__LogLevel__Microsoft Information /M

Considere o seguinte arquivo appsettings.json :

JSON

"Logging": {
"Console": {
"LogLevel": {
"Microsoft.Hosting.Lifetime": "Trace"
}
}
}

O seguinte comando define a configuração anterior no ambiente:

Console

setx Logging__Console__LogLevel__Microsoft.Hosting.Lifetime Trace /M

Em Serviço de Aplicativo do Azure , selecione Nova configuração de aplicativo na


página Definições > Configuração. As configurações do aplicativo do Serviço de
Aplicativo do Azure são:

Criptografadas em repouso e transmitidas por um canal criptografado.


Expostas como variáveis de ambiente.

Para saber mais, confira Aplicativos do Azure: substituir a configuração do aplicativo


usando o portal do Azure.

Para obter mais informações sobre como definir valores de configuração do ASP.NET
Core usando variáveis de ambiente, confira variáveis de ambiente. Para obter
informações sobre como usar outras fontes de configuração, incluindo a linha de
comando, o Azure Key Vault, a Configuração de Aplicativos do Azure, outros formatos
de arquivo e muito mais, consulte Configuração no ASP.NET Core.

Como as regras de filtragem são aplicadas


Quando um objeto ILogger<TCategoryName> é 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 entre 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 .

Registrar a saída da execução do dotnet e do


Visual Studio
Logs criados com os provedores de registro em log padrão são exibidos:

No Visual Studio
Na janela de saída de Depuração ao depurar.
Na janela do servidor Web do ASP.NET Core.
Na janela do console quando o aplicativo é executado com dotnet run .

Logs que começam com as categorias "Microsoft" são de código da estrutura ASP.NET
Core. O ASP.NET Core e o código do aplicativo usam a mesma API de registro em log e
os mesmos provedores.

Categoria do log
Quando um objeto ILogger é criado, uma categoria é especificada. Essa categoria é
incluída em cada mensagem de log criada por essa instância de ILogger . A cadeia de
caracteres da categoria é arbitrária, mas a convenção é usar o nome de classe. Por
exemplo, em um controlador, o nome pode ser "TodoApi.Controllers.TodoController" .
Os aplicativos Web do ASP.NET Core usam ILogger<T> para obter automaticamente
uma instância de ILogger que usa o nome de tipo totalmente qualificado como a
categoria T :

C#

public class PrivacyModel : PageModel


{
private readonly ILogger<PrivacyModel> _logger;

public PrivacyModel(ILogger<PrivacyModel> logger)


{
_logger = logger;
}

public void OnGet()


{
_logger.LogInformation("GET Pages.PrivacyModel called.");
}
}

Para especificar explicitamente a categoria, chame ILoggerFactory.CreateLogger :

C#

public class ContactModel : PageModel


{
private readonly ILogger _logger;

public ContactModel(ILoggerFactory logger)


{
_logger = logger.CreateLogger("MyCategory");
}

public void OnGet()


{
_logger.LogInformation("GET Pages.ContactModel called.");
}

Chamar CreateLogger com um nome fixo pode ser útil quando usado em vários
métodos para que os eventos possam ser organizados por categoria.

ILogger<T> é equivalente a chamar CreateLogger com o nome de tipo totalmente


qualificado de T .

Nível de log
A seguinte tabela lista os valores de LogLevel, o método de extensão de conveniência
Log{LogLevel} e o uso sugerido:

LogLevel Valor Método Descrição

Trace 0 LogTrace Contêm as mensagens mais detalhadas. Essas


mensagens podem conter dados confidenciais do
aplicativo. Essas mensagens são desabilitadas por padrão
e não devem ser habilitadas em um ambiente de
produção.

Debug 1 LogDebug Para depuração e desenvolvimento. Use com cuidado em


produção devido ao alto volume.

Information 2 LogInformation Rastreia o fluxo geral do aplicativo. Pode ter um valor de


longo prazo.

Warning 3 LogWarning Para eventos anormais ou inesperados. Geralmente,


inclui erros ou condições que não fazem com que o
aplicativo falhe.

Error 4 LogError Para erros e exceções que não podem ser manipulados.
Essas mensagens indicam uma falha na operação ou na
solicitação atual, não uma falha em todo o aplicativo.

Critical 5 LogCritical Para falhas que exigem atenção imediata. Exemplos:


cenários de perda de dados, espaço em disco
insuficiente.

None 6 Especifica que uma categoria de log não deve gravar


mensagens.

Na tabela anterior, o LogLevel é listado da severidade menor para a maior.

O primeiro parâmetro do método Log, LogLevel, indica a severidade do log. Em vez de


chamar Log(LogLevel, ...) , a maioria dos desenvolvedores chama os métodos de
extensão Log{LOG LEVEL}, em que o espaço reservado {LOG LEVEL} é o nível de log. Por
exemplo, estas duas chamadas de log são equivalentes em funcionalidades e produzem
o mesmo log:

C#

[HttpGet]
public IActionResult Test1(int id)
{
var routeInfo = ControllerContext.ToCtxString(id);

_logger.Log(LogLevel.Information, MyLogEvents.TestItem, routeInfo);


_logger.LogInformation(MyLogEvents.TestItem, routeInfo);
return ControllerContext.MyDisplayRouteInfo();
}

MyLogEvents.TestItem é a ID do evento. MyLogEvents faz parte do aplicativo de exemplo

e é exibido na seção ID do evento de log.

MyDisplayRouteInfo e ToCtxString são fornecidos pelo pacote NuGet


Rick.Docs.Samples.RouteInfo . Os métodos exibem as informações de rota de
Controller e Razor Page .

O código a seguir cria os logs Information e Warning :

C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT
FOUND", id);
return NotFound();
}

return ItemToDTO(todoItem);
}

No código anterior, o primeiro parâmetro de Log{LOG LEVEL} , MyLogEvents.GetItem , é a


ID do evento de log. O segundo parâmetro é um modelo de mensagem com espaços
reservados para valores de argumento fornecidos pelos parâmetros de método
restantes. Os parâmetros de método serão explicados com posteriormente neste
documento, na seção de modelos de mensagem.

Chame o método Log{LOG LEVEL} adequado para controlar a quantidade de saída de


log gravada em uma mídia de armazenamento específica. Por exemplo:

Em produção:
O log nos níveis Trace ou Information produz um alto volume de mensagens
de log detalhadas. Para controlar os custos e não exceder os limites de
armazenamento de dados, registre mensagens nos níveis Trace e Information
em um armazenamento de dados de alto volume e baixo custo. Considere
limitar Trace e Information a categorias específicas.
O registro em log nos níveis Warning a Critical deve produzir poucas
mensagens de log.
Os custos e os limites de armazenamento geralmente não são preocupantes.
Poucos logs permitem mais flexibilidade nas opções de armazenamento de
dados.
Em desenvolvimento:
Defina como Warning .
Adicione mensagens de Trace ou Information ao solucionar problemas. Para
limitar a saída, defina Trace ou Information somente para as categorias em
investigação.

O ASP.NET Core grava logs para eventos de estrutura. Por exemplo, considere a saída de
log para:

Um aplicativo Razor Pages criado com os modelos do ASP.NET Core.


Registro em log definido como Logging:Console:LogLevel:Microsoft:Information .
Navegação até a página Privacy:

Console

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 GET https://localhost:5001/Privacy
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint '/Privacy'
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
Route matched with {page = "/Privacy"}. Executing page /Privacy
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[101]
Executing handler method DefaultRP.Pages.PrivacyModel.OnGet -
ModelState is Valid
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[102]
Executed handler method OnGet, returned result .
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[103]
Executing an implicit handler method - ModelState is Valid
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[104]
Executed an implicit handler method, returned result
Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[4]
Executed page /Privacy in 74.5188ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint '/Privacy'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 149.3023ms 200 text/html; charset=utf-8

O seguinte JSON define Logging:Console:LogLevel:Microsoft:Information :

JSON

{
"Logging": { // Default, all providers.
"LogLevel": {
"Microsoft": "Warning"
},
"Console": { // Console provider.
"LogLevel": {
"Microsoft": "Information"
}
}
}
}

ID de evento de log
Cada log pode especificar uma ID do evento. O aplicativo de exemplo usa a classe
MyLogEvents para definir IDs de evento:

C#

public class MyLogEvents


{
public const int GenerateItems = 1000;
public const int ListItems = 1001;
public const int GetItem = 1002;
public const int InsertItem = 1003;
public const int UpdateItem = 1004;
public const int DeleteItem = 1005;

public const int TestItem = 3000;

public const int GetItemNotFound = 4000;


public const int UpdateItemNotFound = 4001;
}

C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT
FOUND", id);
return NotFound();
}

return ItemToDTO(todoItem);
}

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:

Console

info: TodoApi.Controllers.TodoItemsController[1002]
Getting item 1
warn: TodoApi.Controllers.TodoItemsController[4000]
Get(1) NOT FOUND

Alguns provedores de log armazenam a ID do evento em um campo, o que permite a


filtragem na ID.

Modelo de mensagem de log


Cada API de log usa um modelo de mensagem. O modelo de mensagem pode conter
espaços reservados para os quais são fornecidos argumentos. Use nomes para os
espaços reservados, não números.

C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT
FOUND", id);
return NotFound();
}

return ItemToDTO(todoItem);
}

A ordem dos parâmetros, não seus nomes de espaço reservado, determina quais
parâmetros são usados para fornecer valores de espaço reservado nas mensagens de
log. No seguinte código, os nomes de parâmetro estão fora da sequência nos espaços
reservados do modelo de mensagem:

C#

string apples = 1;
string pears = 2;
string bananas = 3;

_logger.LogInformation("Parameters: {pears}, {bananas}, {apples}", apples,


pears, bananas);

No entanto, os parâmetros são atribuídos aos espaços reservados na ordem: apples ,


pears , bananas . A mensagem de log reflete a ordem dos parâmetros:

text

Parameters: 1, 2, 3

Essa abordagem permite que os provedores de log implementem o log semântico ou


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,
considere o seguinte método de agente:

C#

_logger.LogInformation("Getting item {Id} at {RequestTime}", id,


DateTime.Now);

Por exemplo, ao registrar em log no Armazenamento de Tabelas do Azure:

Cada entidade da Tabela do Azure pode ter as propriedades ID e RequestTime .


As tabelas com propriedades simplificam as consultas nos dados registrados. Por
exemplo, uma consulta pode encontrar todos os logs em um determinado
intervalo de RequestTime sem precisar analisar o tempo limite da mensagem de
texto.

Registrar exceções em log


Os métodos do agente têm sobrecargas que usam um parâmetro de exceção:

C#

[HttpGet("{id}")]
public IActionResult TestExp(int id)
{
var routeInfo = ControllerContext.ToCtxString(id);
_logger.LogInformation(MyLogEvents.TestItem, routeInfo);

try
{
if (id == 3)
{
throw new Exception("Test exception");
}
}
catch (Exception ex)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, ex, "TestExp({Id})",
id);
return NotFound();
}

return ControllerContext.MyDisplayRouteInfo();
}

MyDisplayRouteInfo e ToCtxString são fornecidos pelo pacote NuGet


Rick.Docs.Samples.RouteInfo . Os métodos exibem as informações de rota de
Controller e Razor Page .

O log de exceções é específico do provedor.

Nível de log padrão


Se o nível de log padrão não for definido, o valor do nível de log padrão será
Information .

Por exemplo, considere o seguinte aplicativo Web:

Criado com os modelos de aplicativo Web ASP.NET.


appsettings.json e appsettings.Development.json excluído ou renomeado.
Com a configuração anterior, o acesso à página de privacidade ou à home page gera
várias mensagens Trace , Debug e Information com Microsoft no nome da categoria.

O seguinte código define o nível de log padrão quando ele não está definido na
configuração:

C#

var builder = WebApplication.CreateBuilder();


builder.Logging.SetMinimumLevel(LogLevel.Warning);

Geralmente, os níveis de log devem ser especificados na configuração, e não no código.

Função Filter
Uma função de filtro é invocada para todos os provedores e as categorias que não têm
regras atribuídas por configuração ou no código:

C#

var builder = WebApplication.CreateBuilder();


builder.Logging.AddFilter((provider, category, logLevel) =>
{
if (provider.Contains("ConsoleLoggerProvider")
&& category.Contains("Controller")
&& logLevel >= LogLevel.Information)
{
return true;
}
else if (provider.Contains("ConsoleLoggerProvider")
&& category.Contains("Microsoft")
&& logLevel >= LogLevel.Information)
{
return true;
}
else
{
return false;
}
});

O código anterior exibe logs de console quando a categoria contém Controller ou


Microsoft e o nível de log é Information ou superior.

Geralmente, os níveis de log devem ser especificados na configuração, e não no código.


ASP.NET Core e EF Core categorias
A seguinte tabela contém algumas categorias usadas pelo ASP.NET Core e pelo Entity
Framework Core, com anotações sobre os logs:

Categoria Observações

Microsoft.AspNetCore Diagnóstico geral de ASP.NET Core.

Microsoft.AspNetCore.DataProtection Quais chaves foram consideradas, encontradas e usadas.

Microsoft.AspNetCore.HostFiltering Hosts permitidos.

Microsoft.AspNetCore.Hosting Quanto tempo levou para que as solicitações de HTTP


fossem concluídas e em que horário foram iniciadas.
Quais assemblies de inicialização de hospedagem foram
carregados.

Microsoft.AspNetCore.Mvc Diagnóstico de Razor e MVC. Model binding, execução


de filtro, compilação de exibição, seleção de ação.

Microsoft.AspNetCore.Routing Informações de correspondência de rotas.

Microsoft.AspNetCore.Server Respostas de início, parada e atividade da conexão.


Informações sobre o certificado HTTPS.

Microsoft.AspNetCore.StaticFiles Arquivos atendidos.

Microsoft.EntityFrameworkCore Diagnóstico geral do Entity Framework Core. Atividade e


configuração do banco de dados, detecção de alterações,
migrações.

Para ver mais categorias na janela do console, defina appsettings.Development.json da


seguinte maneira:

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Trace",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

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.


Dura até que seja descartado.

Os seguintes provedores dão suporte a escopos:

Console
AzureAppServicesFile e AzureAppServicesBlob

Use um escopo por meio do encapsulamento de chamadas de agente em um bloco


using :

C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
TodoItem todoItem;
var transactionId = Guid.NewGuid().ToString();
using (_logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("TransactionId",
transactionId),
}))
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}",
id);

todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound,
"Get({Id}) NOT FOUND", id);
return NotFound();
}
}

return ItemToDTO(todoItem);
}

Provedores de log internos


O ASP.NET Core inclui os seguintes provedores de log como parte da estrutura
compartilhada:

Console
Debug
EventSource
EventLog

Os provedores de log a seguir são fornecidos pela Microsoft, mas não como parte da
estrutura compartilhada. Eles precisam ser instalados como um nuget adicional.

AzureAppServicesFile e AzureAppServicesBlob
ApplicationInsights

O ASP.NET Core não inclui um provedor de log para gravar logs em arquivos. Para
gravar logs em arquivos de um aplicativo ASP.NET Core, considere usar um provedor de
log de terceiros.

Para obter informações sobre stdout e o registro em log de depuração com o Módulo
do ASP.NET Core, consulte Solucionar problemas do ASP.NET Core no Serviço de
Aplicativo do Azure e no IIS e ANCM (Módulo do ASP.NET Core) para IIS.

Console
O provedor Console registra a saída no console. Para obter mais informações sobre
como exibir logs de Console em desenvolvimento, consulte Registrar em log a saída da
execução do dotnet e do Visual Studio.

Depurar
O provedor de Debug grava a saída de log usando a classe System.Diagnostics.Debug.
Chamadas para System.Diagnostics.Debug.WriteLine gravam no provedor de Debug .

No Linux, o local de log do provedor de Debug depende da distribuição e pode ser um


dos seguintes:

/var/log/message

/var/log/syslog

Origem do Evento
O provedor EventSource grava em uma origem do evento multiplataforma com o nome
Microsoft-Extensions-Logging . No Windows, o provedor usa ETW.

ferramentas de rastreamento dotnet

A ferramenta dotnet-trace é uma ferramenta global da CLI multiplataforma que permite


a coleta de rastreamentos do .NET Core de um processo em execução. A ferramenta
coleta dados do provedor de Microsoft.Extensions.Logging.EventSource usando um
LoggingEventSource.

Para obter instruções de instalação, consulte dotnet-trace.

Use as ferramentas de dotnet trace para coletar um rastreamento de um aplicativo:

1. Execute o aplicativo com o comando dotnet run .

2. Determine o PID (identificador de processo) do aplicativo .NET Core:

CLI do .NET

dotnet trace ps

Localize o PID do processo que tem o mesmo nome que o assembly do aplicativo.

3. Execute o comando dotnet trace .

Sintaxe de comando geral:

CLI do .NET

dotnet trace collect -p {PID}


--providers Microsoft-Extensions-Logging:{Keyword}:{Provider Level}
:FilterSpecs=\"
{Logger Category 1}:{Category Level 1};
{Logger Category 2}:{Category Level 2};
...
{Logger Category N}:{Category Level N}\"

Ao usar um shell de comando do PowerShell, coloque o valor --providers entre


aspas simples ( ' ):

CLI do .NET

dotnet trace collect -p {PID}


--providers 'Microsoft-Extensions-Logging:{Keyword}:{Provider
Level}
:FilterSpecs=\"
{Logger Category 1}:{Category Level 1};
{Logger Category 2}:{Category Level 2};
...
{Logger Category N}:{Category Level N}\"'

Em plataformas não Windows, adicione a opção -f speedscope para alterar o


formato do arquivo de rastreamento de saída para speedscope .

A seguinte tabela define a palavra-chave:

Palavra- Descrição
chave

1 Registrar metaeventos sobre o LoggingEventSource . Não registra eventos de


ILogger .

2 Aciona o evento Message quando ILogger.Log() é chamado. Fornece


informações de maneira programática (não formatada).

4 Aciona o evento FormatMessage quando ILogger.Log() é chamado. Fornece a


versão de cadeia de caracteres formatada das informações.

8 Aciona o evento MessageJson quando ILogger.Log() é chamado. Fornece uma


representação JSON dos argumentos.

A seguinte tabela lista os níveis de erro:

Nível do provedor Descrição

0 LogAlways

1 Critical

2 Error

3 Warning

4 Informational

5 Verbose

A análise de um nível de categoria pode ser uma cadeia de caracteres ou um


número:

Valor nomeado de categoria Valor numérico

Trace 0
Valor nomeado de categoria Valor numérico

Debug 1

Information 2

Warning 3

Error 4

Critical 5

O nível do provedor e o nível da categoria:

Têm ordem inversa.


As constantes de cadeia de caracteres não são todas idênticas.

Se nenhum FilterSpecs for especificado, a implementação de EventSourceLogger


tentará converter o nível do provedor em um nível de categoria e o aplicará a
todas as categorias.

Nível do provedor Nível da categoria

Verbose (5) Debug (1)

Informational (4) Information (2)

Warning (3) Warning (3)

Error (2) Error (4)

Critical (1) Critical (5)

Se forem fornecidos FilterSpecs , qualquer categoria incluída na lista usará o nível


de categoria codificado e todas as outras categorias serão filtradas.

Os seguintes exemplos pressupõem que:

Um aplicativo esteja em execução e chamando logger.LogDebug("12345") .


A PID (ID do processo) foi definida por meio de set PID=12345 , em que
12345 é a PID real.

Considere o seguinte código:

CLI do .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:5
O comando anterior:

Captura mensagens de depuração.


Não aplica um FilterSpecs .
Especifica o nível 5, que mapeia a categoria de Depuração.

Considere o seguinte código:

CLI do .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:5:\"FilterSpecs=*:5\"

O comando anterior:

Não captura mensagens de depuração porque o nível 5 da categoria é


Critical .

Fornece um FilterSpecs .

O comando a seguir captura mensagens de depuração porque o nível 1 da


categoria especifica Debug .

CLI do .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:5:\"FilterSpecs=*:1\"

O comando a seguir captura mensagens de depuração porque a categoria


especifica Debug .

CLI do .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:5:\"FilterSpecs=*:Debug\"

Entradas de FilterSpecs para {Logger Category} e {Category Level} representam


condições adicionais de filtragem de log. Separe entradas de FilterSpecs com o
caractere ponto-e-vírgula ; .

Exemplo usando um shell de comando do Windows:

CLI do .NET
dotnet trace collect -p %PID% --providers Microsoft-Extensions-
Logging:4:2:FilterSpecs=\"Microsoft.AspNetCore.Hosting*:4\"

O comando anterior ativa:

O agente de Origem do Evento para produzir cadeias de caracteres


formatadas ( 4 ) para erros ( 2 ).
Registro de Microsoft.AspNetCore.Hosting no nível de registros em log
Informational ( 4 ).

4. Interrompa as ferramentas de rastreamento do dotnet pressionando a tecla Enter


ou Ctrl + C .

O rastreamento é salvo com o nome trace.nettrace na pasta em que o comando


dotnet trace é executado.

5. Abra o rastreamento com Perfview. Abra o arquivo trace.nettrace e explore os


eventos de rastreamento.

Se o aplicativo não compilar o host com WebApplication.CreateBuilder, adicione o


provedor de Origem do Evento à configuração de log do aplicativo.

Para obter mais informações, consulte:

Utilitário de rastreamento para análise de desempenho (dotnet-trace)


(documentação do .NET Core)
Utilitário de rastreamento para análise de desempenho (dotnet-trace)
(documentação do repositório do GitHub para dotnet/diagnóstico)
LoggingEventSource
EventLevel
Perfview: útil para exibir rastreamentos de Origem do Evento.

Perfview

Use o utilitário PerfView para coletar e exibir logs. 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 Core.

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 * no início da cadeia de caracteres.
EventLog do Windows
O provedor EventLog envia a saída de log para o Log de Eventos do Windows. Diferente
dos outros provedores, o provedor EventLog não herda as configurações de não
provedor padrão. Se as configurações de log EventLog não forem especificadas, elas
serão LogLevel.Warning por padrão.

Para registrar eventos inferiores a LogLevel.Warning, defina explicitamente o nível de


log. O seguinte exemplo define o nível de log padrão do Log de Eventos como
LogLevel.Information:

JSON

"Logging": {
"EventLog": {
"LogLevel": {
"Default": "Information"
}
}
}

Sobrecargas de AddEventLog podem passar EventLogSettings. Se for null ou não for


especificado, as seguintes configurações padrão serão usadas:

LogName : "Aplicativo"

SourceName : "Runtime do .NET"


MachineName : o nome do computador local é usado.

O seguinte código altera o SourceName do valor padrão de ".NET Runtime" para MyLogs :

C#

var builder = WebApplication.CreateBuilder();


builder.Logging.AddEventLog(eventLogSettings =>
{
eventLogSettings.SourceName = "MyLogs";
});

Serviço de aplicativo do Azure


O pacote do provedor Microsoft.Extensions.Logging.AzureAppServices grava logs em
arquivos de texto no sistema de arquivos de um aplicativo do Serviço de Aplicativo do
Azure e no armazenamento de blobs em uma conta de Armazenamento do Azure.
O pacote do provedor não está incluído na estrutura compartilhada. Para usar o
provedor, adicione o pacote do provedor ao projeto.

Para definir as configurações do provedor, use AzureFileLoggerOptions e


AzureBlobLoggerOptions, conforme mostrado no exemplo a seguir:

C#

using Microsoft.Extensions.Logging.AzureAppServices;

var builder = WebApplication.CreateBuilder();


builder.Logging.AddAzureWebAppDiagnostics();
builder.Services.Configure<AzureFileLoggerOptions>(options =>
{
options.FileName = "azure-diagnostics-";
options.FileSizeLimit = 50 * 1024;
options.RetainedFileCountLimit = 5;
});
builder.Services.Configure<AzureBlobLoggerOptions>(options =>
{
options.BlobName = "log.txt";
});

Quando implantado no Serviço de Aplicativo do Azure, o aplicativo usa as configurações


na seção Logs do Serviço de Aplicativo da página Serviço de Aplicativo do portal do
Azure. Quando as configurações a seguir são atualizadas, as alterações entram em vigor
imediatamente sem a necessidade de uma reinicialização ou reimplantação do
aplicativo.

Log de aplicativo (Sistema de Arquivos)


Log de aplicativo (Blob)

O local padrão dos arquivos de log é a pasta D:\\home\\LogFiles\\Application , e o


nome do arquivo padrão é diagnostics-yyyymmdd.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 padrão
do blob é {app-name}{timestamp}/yyyy/mm/dd/hh/{guid}-applicationLog.txt .

O provedor registra somente quando o projeto é executado no ambiente do Azure.

Fluxo de log do Azure

O streaming de log do Azure dá suporte à exibição da atividade de log em tempo


usando:

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 do Serviço de Aplicativo no portal do aplicativo.


Defina Habilitar o log de aplicativo (sistema de arquivos) como Ativada.
Escolha o Nível de log. Essa configuração se aplica somente ao streaming de log
do Azure.

Navegue até a página Fluxo de Log para exibir os logs. As mensagens são registradas
com a interface ILogger .

Azure Application Insights


O pacote do provedor Microsoft.Extensions.Logging.ApplicationInsights grava logs no
Azure Application Insights. O Application Insights é um serviço que monitora um
aplicativo web e fornece ferramentas para consultar e analisar os dados de telemetria.
Se você usar esse provedor, poderá consultar e analisar os logs usando as ferramentas
do Application Insights.

O provedor de registro em log é incluído como uma dependência de


Microsoft.ApplicationInsights.AspNetCore , que é o pacote que fornece toda a
telemetria disponível para o ASP.NET Core. Se você usar esse pacote, não precisará
instalar o pacote de provedor.

O pacote Microsoft.ApplicationInsights.Web é para o ASP.NET 4.x, não para o ASP.NET


Core.

Para saber mais, consulte os recursos a seguir:

Visão geral do Application Insights


Application Insights para aplicativos ASP.NET Core: comece aqui se você deseja
implementar toda a gama de telemetria do Application Insights com o registro em
log.
Logs do ILogger ApplicationInsightsLoggerProvider para o .NET Core: comece aqui
se você quiser implementar o provedor de log sem o restante da telemetria do
Application Insights.
Adaptadores de registro em log do Application Insights
Instalar, configurar e inicializar tutorial interativo do SDK do Application Insights.

Provedores de log de terceiros


Estruturas de log de terceiros que funcionam com o ASP.NET Core:
elmah.io (repositório GitHub )
Gelf (repositório do GitHub )
JSNLog (repositório do GitHub )
KissLog.net (Repositório do GitHub )
Log4Net (repositório do GitHub )
NLog (repositório GitHub )
PLogger (repositório do GitHub )
Sentry (repositório GitHub )
Serilog (repositório GitHub )
Stackdriver (repositório Github )

Algumas estruturas de terceiros podem fazer o log semântico, também conhecido como
registro em log estruturado .

Usar uma estrutura de terceiros é semelhante ao uso de um dos provedores internos:

1. Adicione um pacote NuGet ao projeto.


2. Chame um método de extensão ILoggerFactory fornecido pela estrutura de
registros.

Para saber mais, consulte a documentação de cada provedor. Não há suporte para
provedores de log de terceiros na Microsoft.

Sem métodos de agente assíncronos


O registro em log deve ser tão rápido que não justifique o custo de desempenho de
código assíncrono. Se o armazenamento de dados em log estiver lento, não grave
diretamente nele. Grave as mensagens de log em um armazenamento rápido primeiro e
depois passe-as para um armazenamento lento. Por exemplo, se você estiver enviado
logs ao SQL Server, não faça isso diretamente em um método Log , pois os métodos
Log são síncronos. Em vez disso, adicione mensagens de log de forma síncrona a uma
fila na memória e faça com que uma função de trabalho de plano de fundo efetue pull
das mensagens para fora da fila para fazer o trabalho assíncrono de envio de dados por
push para o SQL Server. Para saber mais, consulte Diretrizes sobre como fazer logon em
uma fila de mensagens para armazenamentos de dados lentos
(dotnet/AspNetCore.Docs #11801) .

Alterar os níveis de log em um aplicativo em


execução
A API de Log não inclui um cenário para alterar os níveis de log enquanto um aplicativo
está em execução. No entanto, alguns provedores de configuração conseguem
recarregar a configuração, que entra em vigor imediatamente na configuração de log.
Por exemplo, o Provedor de Configuração de Arquivos recarrega a configuração de log
por padrão. Se a configuração for alterada no código enquanto um aplicativo estiver em
execução, o aplicativo poderá chamar IConfigurationRoot.Reload para atualizar a
configuração de log do aplicativo.

ILogger e ILoggerFactory
As interfaces e implementações ILogger<TCategoryName> e ILoggerFactory estão
incluídas no SDK do .NET Core. Elas também estão disponíveis nos seguintes pacotes
NuGet:

As interfaces estão em Microsoft.Extensions.Logging.Abstractions .


As implementações padrão estão em Microsoft.Extensions.Logging .

Aplicar regras de filtro de log no código


A abordagem preferencial para definir regras de filtro de log é usar a Configuração.

O exemplo a seguir mostra como registrar regras de filtro no código:

C#

using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Logging.Debug;

var builder = WebApplication.CreateBuilder();


builder.Logging.AddFilter("System", LogLevel.Debug);
builder.Logging.AddFilter<DebugLoggerProvider>("Microsoft",
LogLevel.Information);
builder.Logging.AddFilter<ConsoleLoggerProvider>("Microsoft",
LogLevel.Trace);

logging.AddFilter("System", LogLevel.Debug) especifica a categoria System e o nível de

log Debug . O filtro é aplicado a todos os provedores porque um provedor específico não
foi configurado.

AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information) especifica:

O provedor de log Debug .


O nível de log Information e superiores.
Todas as categorias começando com "Microsoft" .

Registre automaticamente o escopo com


SpanId , TraceId , ParentId , Baggage e Tags .
As bibliotecas de log criam implicitamente um objeto de escopo com SpanId , TraceId ,
ParentId , Baggage e Tags . Esse comportamento é configurado por meio de
ActivityTrackingOptions.

C#

var loggerFactory = LoggerFactory.Create(logging =>


{
logging.Configure(options =>
{
options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
|
ActivityTrackingOptions.TraceId
|
ActivityTrackingOptions.ParentId
|
ActivityTrackingOptions.Baggage
|
ActivityTrackingOptions.Tags;
}).AddSimpleConsole(options =>
{
options.IncludeScopes = true;
});
});

Se o cabeçalho de solicitação http traceparent estiver definido, o ParentId no escopo


do log mostrará o W3C parent-id do cabeçalho traceparent de entrada e o SpanId no
escopo do log mostrará o parent-id atualizado para a próxima etapa/intervalo de saída.
Para obter mais informações, consulte Alterando o campo traceparent .

Criar um agente personalizado


Para criar um agente personalizado, consulte Implementar um provedor de log
personalizado no .NET.

Recursos adicionais
Fonte Microsoft.Extensions.Logging no GitHub
Exibir ou baixar um código de exemplo (como baixar).
Registro em log de alto desempenho com o LoggerMessage no ASP.NET Core
Bugs de log devem ser criados no repositório do GitHub dotnet/runtime .
Registro em log de Blazor no ASP.NET Core
Registro em log HTTP no ASP.NET Core
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

O Registro em Log HTTP é um middleware que registra informações sobre a entrada de


solicitações HTTP e respostas HTTP. O registro em log HTTP fornece logs de:

Informações de solicitação HTTP


Propriedades comuns
Cabeçalhos
Corpo
Informações de resposta HTTP

O registro em log HTTP é útil em vários cenários para:

Registrar informações sobre solicitações e respostas de entrada.


Filtrar quais partes da solicitação e da resposta são registradas.
Filtrar quais cabeçalhos registrar.

O registro em log HTTP pode reduzir o desempenho de um aplicativo, especialmente ao


registrar em log os corpos de solicitação e resposta. Considere o impacto sobre o
desempenho ao selecionar campos para registrar. Teste o impacto sobre o desempenho
das propriedades de log selecionadas.

2 Aviso

O registro em log HTTP pode registrar PII (informações de identificação pessoal).


Considere o risco e evite registrar informações confidenciais.

Habilitando o registro em log HTTP


O registro em log HTTP é habilitado com UseHttpLogging, que adiciona o middleware
de registro em log HTTP.

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.UseHttpLogging();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();

app.MapGet("/", () => "Hello World!");

app.Run();

Por padrão, o registro em log HTTP registra propriedades comuns, como caminho,
código de status e cabeçalhos, para solicitações e respostas. Adicione a seguinte linha
ao arquivo appsettings.Development.json no nível "LogLevel": { para que os logs
HTTP sejam exibidos:

JSON

"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

A saída é registrada como uma mensagem em LogLevel.Information .

Opções de registro em log HTTP


Para configurar o middleware de registro em log HTTP, chame AddHttpLogging em
Program.cs .

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };

await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

LoggingFields

HttpLoggingOptions.LoggingFields é um sinalizador de enumeração que configura


partes específicas da solicitação e da resposta para registrar.
HttpLoggingOptions.LoggingFields usa como padrão RequestPropertiesAndHeaders |

ResponsePropertiesAndHeaders.

RequestHeaders

Headers são um conjunto de cabeçalhos de solicitação HTTP que têm permissão para
ser registrados. Os valores de cabeçalho são registrados apenas para nomes de
cabeçalho que estão nesta coleção. O código a seguir registra o cabeçalho de
solicitação "sec-ch-ua" . Se logging.RequestHeaders.Add("sec-ch-ua"); for removido, o
valor do cabeçalho da solicitação "sec-ch-ua" será redigido. O seguinte código
realçado chama HttpLoggingOptions.RequestHeaders e
HttpLoggingOptions.ResponseHeaders:

C#

using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };

await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

MediaTypeOptions

MediaTypeOptions fornece configuração para selecionar qual codificação usar para um


tipo de mídia específico.

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };

await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Métodos MediaTypeOptions
AddText
AddBinary
Clear

RequestBodyLogLimit e ResponseBodyLogLimit

RequestBodyLogLimit
ResponseBodyLogLimit

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };

await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();
W3CLogger no ASP.NET Core
Artigo • 28/11/2022 • 3 minutos para o fim da leitura

O W3CLogger é um middleware que grava arquivos de log no formato padrão W3C .


Os logs contêm informações sobre solicitações HTTP e respostas HTTP. O W3CLogger
fornece logs de:

Informações de solicitação HTTP


Propriedades comuns
Cabeçalhos
Informações de resposta HTTP
Metadados sobre o par de solicitação/resposta (data/hora de início, tempo gasto)

O W3CLogger é útil em vários cenários para:

Registrar informações sobre solicitações e respostas de entrada.


Filtrar quais partes da solicitação e da resposta são registradas.
Filtrar quais cabeçalhos registrar.

O W3CLogger pode reduzir o desempenho de um aplicativo. Considere o impacto


sobre o desempenho ao selecionar campos para registrar – a redução de desempenho
aumentará à medida que você registrar mais propriedades. Teste o impacto sobre o
desempenho das propriedades de log selecionadas.

2 Aviso

O W3CLogger pode registrar PII (informações de identificação pessoal). Considere


o risco e evite registrar informações confidenciais. Por padrão, campos que podem
conter PII não são registrados.

Habilitar o W3CLogger
Habilite o W3CLogger com UseW3CLogging, que adiciona o middleware W3CLogger:

C#

var app = builder.Build();

app.UseW3CLogging();

app.UseRouting();
Por padrão, o W3CLogger registra propriedades comuns, como caminho, código de
status, data, hora e protocolo. Todas as informações sobre um par de
solicitação/resposta são gravadas na mesma linha.

#Version: 1.0
#Start-Date: 2021-09-29 22:18:28
#Fields: date time c-ip s-computername s-ip s-port cs-method cs-uri-stem cs-
uri-query sc-status time-taken cs-version cs-host cs(User-Agent) cs(Referer)
2021-09-29 22:18:28 ::1 DESKTOP-LH3TLTA ::1 5000 GET / - 200 59.9171
HTTP/1.1 localhost:5000 Mozilla/5.0+
(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+
(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36 -
2021-09-29 22:18:28 ::1 DESKTOP-LH3TLTA ::1 5000 GET / - 200 0.1802 HTTP/1.1
localhost:5000 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+
(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36 -
2021-09-29 22:18:30 ::1 DESKTOP-LH3TLTA ::1 5000 GET / - 200 0.0966 HTTP/1.1
localhost:5000 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+
(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36 -

Opções do W3CLogger
Para configurar o middleware do W3CLogger, chame AddW3CLogging em Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddW3CLogging(logging =>
{
// Log all W3C fields
logging.LoggingFields = W3CLoggingFields.All;

logging.AdditionalRequestHeaders.Add("x-forwarded-for");
logging.AdditionalRequestHeaders.Add("x-client-ssl-protocol");
logging.FileSizeLimit = 5 * 1024 * 1024;
logging.RetainedFileCountLimit = 2;
logging.FileName = "MyLogFile";
logging.LogDirectory = @"C:\logs";
logging.FlushInterval = TimeSpan.FromSeconds(2);
});

LoggingFields

W3CLoggerOptions.LoggingFields é uma enumeração de sinalizador de bit que


configura partes específicas da solicitação e resposta ao log e outras informações sobre
a conexão. Por padrão, LoggingFields inclui todos os campos possíveis, exceto
UserName e Cookie . Para obter uma lista completa dos campos disponíveis, consulte
W3CLoggingFields.
Roteamento no ASP.NET Core
Artigo • 28/11/2022 • 114 minutos para o fim da leitura

Por Ryan Nowak , Kirk Larkin e Rick Anderson

O roteamento é responsável por corresponder solicitações HTTP de entrada e expedir


essas solicitações para os pontos de extremidade executáveis do aplicativo. Os pontos
de extremidade são as unidades do aplicativo de código executável de manipulação de
solicitações. Os pontos de extremidade são definidos no aplicativo e configurados
quando o aplicativo é iniciado. O processo de correspondência de ponto de
extremidade pode extrair valores da URL da solicitação e fornecer esses valores para
processamento de solicitações. Usando informações de ponto de extremidade do
aplicativo, o roteamento também é capaz de gerar URLs mapeadas para pontos de
extremidade.

Os aplicativos podem configurar o roteamento usando:

Controladores
Razor Pages
SignalR
Serviços gRPC
Middleware habilitado para ponto de extremidade, como Verificações de
Integridade.
Delegados e lambdas registrados com roteamento.

Este artigo aborda detalhes de baixo nível do roteamento de ASP.NET Core. Para obter
informações sobre como configurar o roteamento:

Para controladores, consulte Roteamento para ações do controlador no ASP.NET


Core.
Para Razor convenções de Páginas, consulte Razor Convenções de rota e aplicativo
de páginas no ASP.NET Core.

Conceitos básicos sobre roteamento


O código a seguir mostra um exemplo básico de roteamento:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");


app.Run();

O exemplo anterior inclui um único ponto de extremidade usando o MapGet método :

Quando uma solicitação HTTP GET é enviada para a URL / raiz:


O delegado de solicitação é executado.
Hello World! é gravado na resposta HTTP.
Se o método de solicitação não GET for ou a URL raiz não / for , nenhuma rota
corresponderá e um HTTP 404 será retornado.

O roteamento usa um par de middleware, registrados por UseRouting e UseEndpoints:

UseRouting adiciona a correspondência de rotas ao pipeline de middleware. Esse

middleware examina o conjunto de pontos de extremidade definidos no aplicativo


e seleciona a melhor correspondência com base na solicitação.
UseEndpoints adiciona a execução do ponto de extremidade ao pipeline de

middleware. Ele executa o delegado associado ao ponto de extremidade


selecionado.

Normalmente, os aplicativos não precisam chamar UseRouting ou UseEndpoints .


WebApplicationBuilder configura um pipeline de middleware que encapsula o
middleware adicionado com Program.cs UseRouting e UseEndpoints . No entanto, os
aplicativos podem alterar a ordem na qual UseRouting e UseEndpoints são executados
chamando esses métodos explicitamente. Por exemplo, o código a seguir faz uma
chamada explícita para UseRouting :

C#

app.Use(async (context, next) =>


{
// ...
await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

No código anterior:

A chamada para app.Use registra um middleware personalizado que é executado


no início do pipeline.
A chamada para UseRouting configura o middleware de correspondência de rota a
ser executado após o middleware personalizado.
O ponto de extremidade registrado com MapGet é executado no final do pipeline.

Se o exemplo anterior não incluísse uma chamada para UseRouting , o middleware


personalizado seria executado após o middleware correspondente à rota.

Pontos de extremidade
O MapGet método é usado para definir um ponto de extremidade. Um ponto de
extremidade é algo que pode ser:

Selecionado, correspondendo à URL e ao método HTTP.


Executado executando o delegado.

Os pontos de extremidade que podem ser correspondidos e executados pelo aplicativo


são configurados no UseEndpoints . Por exemplo, MapGet, MapPoste métodos
semelhantes conectam delegados de solicitação ao sistema de roteamento. Métodos
adicionais podem ser usados para conectar ASP.NET Core recursos da estrutura ao
sistema de roteamento:

Páginas de MapaRazorpara Razor Páginas


MapControllers para controladores
MapHub<THub> para SignalR
MapGrpcService<TService> para gRPC

O exemplo a seguir mostra o roteamento com um modelo de rota mais sofisticado:

C#

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

A cadeia de caracteres /hello/{name:alpha} é um modelo de rota. Um modelo de rota


é usado para configurar como o ponto de extremidade é correspondido. Nesse caso, o
modelo corresponde a:

Uma URL como /hello/Docs


Qualquer caminho de URL que comece com /hello/ seguido por uma sequência
de caracteres alfabéticos. :alpha aplica uma restrição de rota que corresponde
apenas a caracteres alfabéticos. As restrições de rota são explicadas
posteriormente neste artigo.

O segundo segmento do caminho da URL, {name:alpha} :


Está associado ao name parâmetro .
É capturado e armazenado em HttpRequest.RouteValues.

O exemplo a seguir mostra o roteamento com verificações de integridade e autorização:

C#

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

O exemplo anterior demonstra como:

O middleware de autorização pode ser usado com o roteamento.


Os pontos de extremidade podem ser usados para configurar o comportamento
de autorização.

A MapHealthChecks chamada adiciona um ponto de extremidade de verificação de


integridade. Encadear RequireAuthorization essa chamada anexa uma política de
autorização ao ponto de extremidade.

Chamar UseAuthentication e UseAuthorization adicionar o middleware de autenticação


e autorização. Esses middlewares são colocados entre UseRouting e UseEndpoints para
que possam:

Veja qual ponto de extremidade foi selecionado por UseRouting .


Aplique uma política de autorização antes UseEndpoints de expedir para o ponto
de extremidade.

Metadados de ponto de extremidade


No exemplo anterior, há dois pontos de extremidade, mas apenas o ponto de
extremidade de verificação de integridade tem uma política de autorização anexada. Se
a solicitação corresponder ao ponto de extremidade de verificação de integridade,
/healthz , uma verificação de autorização será executada. Isso demonstra que os pontos

de extremidade podem ter dados extras anexados a eles. Esses dados extras são
chamados de metadados de ponto de extremidade:

Os metadados podem ser processados por middleware com reconhecimento de


roteamento.
Os metadados podem ser de qualquer tipo .NET.
Conceitos de roteamento
O sistema de roteamento se baseia no pipeline do middleware adicionando o conceito
avançado de ponto de extremidade . Os pontos de extremidade representam unidades
da funcionalidade do aplicativo que são distintas umas das outras em termos de
roteamento, autorização e qualquer número de sistemas de ASP.NET Core.

ASP.NET Core definição de ponto de extremidade


Um ponto de extremidade ASP.NET Core é:

Executável: tem um RequestDelegate.


Extensível: tem uma coleção de metadados .
Selecionável: opcionalmente, tem informações de roteamento.
Enumerável: a coleção de pontos de extremidade pode ser listada recuperando o
EndpointDataSource da DI.

O código a seguir mostra como recuperar e inspecionar o ponto de extremidade


correspondente à solicitação atual:

C#

app.Use(async (context, next) =>


{
var currentEndpoint = context.GetEndpoint();

if (currentEndpoint is null)
{
await next(context);
return;
}

Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

if (currentEndpoint is RouteEndpoint routeEndpoint)


{
Console.WriteLine($" - Route Pattern:
{routeEndpoint.RoutePattern}");
}

foreach (var endpointMetadata in currentEndpoint.Metadata)


{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}

await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");


O ponto de extremidade, se selecionado, pode ser recuperado do HttpContext . Suas
propriedades podem ser inspecionadas. Os objetos de ponto de extremidade são
imutáveis e não podem ser modificados após a criação. O tipo mais comum de ponto
de extremidade é um RouteEndpoint. RouteEndpoint inclui informações que permitem
que ela seja selecionada pelo sistema de roteamento.

No código anterior, aplicativo. Usar configura um middleware embutido.

O código a seguir mostra que, dependendo de onde app.Use é chamado no pipeline,


pode não haver um ponto de extremidade:

C#

// Location 1: before routing runs, endpoint is always null here.


app.Use(async (context, next) =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ??
"(null)"}");
await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing


found a match.
app.Use(async (context, next) =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ??
"(null)"}");
await next(context);
});

// Location 3: runs when this endpoint matches


app.MapGet("/", (HttpContext context) =>
{
Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ??
"(null)"}");
return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no


match.
app.Use(async (context, next) =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ??
"(null)"}");
await next(context);
});
O exemplo anterior adiciona Console.WriteLine instruções que exibem se um ponto de
extremidade foi selecionado ou não. Para maior clareza, o exemplo atribui um nome de
exibição ao ponto de extremidade fornecido / .

O exemplo anterior também inclui chamadas para UseRouting e UseEndpoints para


controlar exatamente quando esses middleware são executados dentro do pipeline.

A execução deste código com uma URL de / exibe:

txt

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

A execução desse código com qualquer outra URL é exibida:

txt

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Essa saída demonstra que:

O ponto de extremidade é sempre nulo antes UseRouting de ser chamado.


Se uma correspondência for encontrada, o ponto de extremidade não será nulo
entre UseRouting e UseEndpoints.
O UseEndpoints middleware é terminal quando uma correspondência é
encontrada. O middleware de terminal é definido posteriormente neste artigo.
O middleware após UseEndpoints a execução somente quando nenhuma
correspondência for encontrada.

O UseRouting middleware usa o SetEndpoint método para anexar o ponto de


extremidade ao contexto atual. É possível substituir o UseRouting middleware pela
lógica personalizada e ainda obter os benefícios de usar pontos de extremidade. Os
pontos de extremidade são um primitivo de baixo nível, como middleware, e não são
acoplados à implementação de roteamento. A maioria dos aplicativos não precisa
substituir pela UseRouting lógica personalizada.

O UseEndpoints middleware foi projetado para ser usado em conjunto com o


UseRouting middleware. A lógica principal para executar um ponto de extremidade não
é complicada. Use GetEndpoint para recuperar o ponto de extremidade e, em seguida,
invoque sua RequestDelegate propriedade.

O código a seguir demonstra como o middleware pode influenciar ou reagir ao


roteamento:

C#

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>


{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>
() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT:
{DateTime.UtcNow}");
}

await next(context);
});

app.MapGet("/", () => "Audit isn't required.");


app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());

C#

public class RequiresAuditAttribute : Attribute { }

O exemplo anterior demonstra dois conceitos importantes:

O middleware pode ser executado antes UseRouting para modificar os dados nos
quais o roteamento opera.
Normalmente, o middleware que aparece antes do roteamento modifica
alguma propriedade da solicitação, como UseRewriter,
UseHttpMethodOverrideou UsePathBase.
O middleware pode ser executado entre UseRouting e UseEndpoints para
processar os resultados do roteamento antes que o ponto de extremidade seja
executado.
Middleware executado entre UseRouting e UseEndpoints :
Geralmente inspeciona metadados para entender os pontos de extremidade.
Muitas vezes toma decisões de segurança, conforme feito por
UseAuthorization e UseCors .
A combinação de middleware e metadados permite configurar políticas por
ponto de extremidade.

O código anterior mostra um exemplo de um middleware personalizado que dá suporte


a políticas por ponto de extremidade. O middleware grava um log de auditoria de
acesso a dados confidenciais no console. O middleware pode ser configurado para
auditar um ponto de extremidade com os RequiresAuditAttribute metadados. Este
exemplo demonstra um padrão de aceitação em que apenas pontos de extremidade
marcados como confidenciais são auditados. É possível definir essa lógica ao contrário,
auditando tudo o que não está marcado como seguro, por exemplo. O sistema de
metadados do ponto de extremidade é flexível. Essa lógica pode ser projetada de
qualquer forma que se adapte ao caso de uso.

O código de exemplo anterior destina-se a demonstrar os conceitos básicos dos pontos


de extremidade. O exemplo não se destina ao uso em produção. Uma versão mais
completa de um middleware de log de auditoria :

Faça logon em um arquivo ou banco de dados.


Inclua detalhes como o usuário, o endereço IP, o nome do ponto de extremidade
confidencial e muito mais.

Os metadados RequiresAuditAttribute da política de auditoria são definidos como um


Attribute para facilitar o uso com estruturas baseadas em classe, como controladores e
SignalR. Ao usar a rota para o código:

Os metadados são anexados com uma API do construtor.


As estruturas baseadas em classe incluem todos os atributos no método e na
classe correspondentes ao criar pontos de extremidade.

As práticas recomendadas para tipos de metadados são defini-los como interfaces ou


atributos. Interfaces e atributos permitem a reutilização de código. O sistema de
metadados é flexível e não impõe limitações.

Comparar middleware de terminal com roteamento


O exemplo a seguir demonstra o middleware terminal e o roteamento:

C#

// Approach 1: Terminal Middleware.


app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Terminal Middleware.");
return;
}

await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

O estilo do middleware mostrado com Approach 1: é o middleware terminal. Ele é


chamado de middleware terminal porque faz uma operação de correspondência:

A operação de correspondência no exemplo anterior é Path == "/" para o


middleware e Path == "/Routing" para roteamento.
Quando uma correspondência é bem-sucedida, ela executa algumas
funcionalidades e retorna, em vez de invocar o next middleware.

Ele é chamado de middleware de terminal porque encerra a pesquisa, executa algumas


funcionalidades e retorna.

A lista a seguir compara o middleware do terminal com o roteamento:

Ambas as abordagens permitem encerrar o pipeline de processamento:


O middleware encerra o pipeline retornando em vez de invocar next .
Os pontos de extremidade são sempre terminais.
O middleware do terminal permite posicionar o middleware em um local arbitrário
no pipeline:
Os pontos de extremidade são executados na posição de UseEndpoints.
O middleware do terminal permite que o código arbitrário determine quando o
middleware corresponde:
O código de correspondência de rota personalizado pode ser detalhado e difícil
de gravar corretamente.
O roteamento fornece soluções simples para aplicativos típicos. A maioria dos
aplicativos não exige código de correspondência de rota personalizado.
Interface de pontos de extremidade com middleware como UseAuthorization e
UseCors .
Usar um middleware de terminal com UseAuthorization ou UseCors requer
interfacagem manual com o sistema de autorização.

Um ponto de extremidade define ambos:

Um delegado para processar solicitações.


Uma coleção de metadados arbitrários. Os metadados são usados para
implementar preocupações transversais com base em políticas e configuração
anexadas a cada ponto de extremidade.

O middleware do terminal pode ser uma ferramenta eficaz, mas pode exigir:

Uma quantidade significativa de codificação e teste.


Integração manual com outros sistemas para alcançar o nível desejado de
flexibilidade.

Considere a integração com o roteamento antes de escrever um middleware de


terminal.

O middleware de terminal existente que se integra ao Mapa ou MapWhen geralmente


pode ser transformado em um ponto de extremidade com reconhecimento de
roteamento. MapHealthChecks demonstra o padrão para router-ware:

Escreva um método de extensão em IEndpointRouteBuilder.


Crie um pipeline de middleware aninhado usando CreateApplicationBuilder.
Anexe o middleware ao novo pipeline. Nesse caso, UseHealthChecks.
Build o pipeline de middleware em um RequestDelegate.
Chame Map e forneça o novo pipeline de middleware.
Retornar o objeto do construtor fornecido pelo Map do método de extensão.

O código a seguir mostra o uso de MapHealthChecks:

C#

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

O exemplo anterior mostra por que o retorno do objeto do construtor é importante.


Retornar o objeto builder permite que o desenvolvedor do aplicativo configure políticas
como autorização para o ponto de extremidade. Neste exemplo, o middleware de
verificações de integridade não tem integração direta com o sistema de autorização.

O sistema de metadados foi criado em resposta aos problemas encontrados por autores
de extensibilidade usando middleware terminal. É problemático para cada middleware
implementar sua própria integração com o sistema de autorização.

Correspondência de URL
É o processo pelo qual o roteamento corresponde a uma solicitação de entrada
para um ponto de extremidade.
É baseado em dados no caminho e cabeçalhos da URL.
Pode ser estendido para considerar quaisquer dados na solicitação.

Quando um middleware de roteamento é executado, ele define um Endpoint e roteia


valores para um recurso de solicitaçãoHttpContext no da solicitação atual:

Chamar HttpContext.GetEndpoint obtém o ponto de extremidade.


HttpRequest.RouteValues obtém a coleção de valores de rota.

O middleware em execução após o middleware de roteamento pode inspecionar o


ponto de extremidade e tomar medidas. Por exemplo, um middleware de autorização
pode interrogar a coleção de metadados do ponto de extremidade para uma política de
autorização. Depois que todos os middlewares no pipeline de processamento da
solicitação forem executados, o representante do ponto de extremidade selecionado
será invocado.

O sistema de roteamento no roteamento de ponto de extremidade é responsável por


todas as decisões de expedição. Como o middleware aplica políticas com base no ponto
de extremidade selecionado, é importante que:

Qualquer decisão que possa afetar a expedição ou a aplicação de políticas de


segurança é tomada dentro do sistema de roteamento.

2 Aviso

Para compatibilidade com versões anteriores, quando um delegado de ponto de


extremidade Controller ou Razor Pages é executado, as propriedades de
RouteContext.RouteData são definidas como valores apropriados com base no
processamento de solicitação executado até agora.

O RouteContext tipo será marcado como obsoleto em uma versão futura:

Migre RouteData.Values para HttpRequest.RouteValues .


Migre RouteData.DataTokens para recuperar IDataTokensMetadata dos
metadados do ponto de extremidade.

A correspondência de URL opera em um conjunto configurável de fases. Em cada fase, a


saída é um conjunto de correspondências. O conjunto de correspondências pode ser
reduzido ainda mais pela próxima fase. A implementação de roteamento não garante
uma ordem de processamento para pontos de extremidade correspondentes. Todas as
correspondências possíveis são processadas de uma só vez. As fases de
correspondência de URL ocorrem na ordem a seguir. ASP.NET Core:

1. Processa o caminho da URL em relação ao conjunto de pontos de extremidade e


seus modelos de rota, coletando todas as correspondências.
2. Usa a lista anterior e remove correspondências que falham com restrições de rota
aplicadas.
3. Usa a lista anterior e remove as correspondências que falham no conjunto de
MatcherPolicy instâncias.
4. Usa o EndpointSelector para tomar uma decisão final da lista anterior.

A lista de pontos de extremidade é priorizada de acordo com:

O RouteEndpoint.Order
A precedência do modelo de rota

Todos os pontos de extremidade correspondentes são processados em cada fase até


que o EndpointSelector seja atingido. O EndpointSelector é a fase final. Ele escolhe o
ponto de extremidade de prioridade mais alta das correspondências como a melhor
correspondência. Se houver outras correspondências com a mesma prioridade que a
melhor correspondência, uma exceção de correspondência ambígua será gerada.

A precedência de rota é calculada com base em um modelo de rota mais específico que
recebe uma prioridade mais alta. Por exemplo, considere os modelos /hello e
/{message} :

Ambos correspondem ao caminho /hello da URL .


/hello é mais específico e, portanto, prioridade mais alta.

Em geral, a precedência de rota faz um bom trabalho de escolher a melhor


correspondência para os tipos de esquemas de URL usados na prática. Use Order
somente quando necessário para evitar uma ambiguidade.

Devido aos tipos de extensibilidade fornecidos pelo roteamento, não é possível que o
sistema de roteamento compute antecipadamente as rotas ambíguas. Considere um
exemplo, como os modelos /{message:alpha} de rota e /{message:int} :

A alpha restrição corresponde apenas a caracteres alfabéticos.


A int restrição corresponde apenas a números.
Esses modelos têm a mesma precedência de rota, mas não há uma ÚNICA URL
que ambos correspondam.
Se o sistema de roteamento relatasse um erro de ambiguidade na inicialização, ele
bloquearia esse caso de uso válido.
2 Aviso

A ordem das operações internas UseEndpoints não influencia o comportamento


do roteamento, com uma exceção. MapControllerRoute e MapAreaRoute
atribuem automaticamente um valor de pedido aos pontos de extremidade com
base na ordem em que são invocados. Isso simula o comportamento de longo
prazo dos controladores sem que o sistema de roteamento forneça as mesmas
garantias que as implementações de roteamento mais antigas.

Roteamento de ponto de extremidade em ASP.NET Core:

Não tem o conceito de rotas.


Não fornece garantias de ordenação. Todos os pontos de extremidade são
processados de uma só vez.

Precedência do modelo de rota e ordem de seleção de


ponto de extremidade
A precedência do modelo de rota é um sistema que atribui a cada modelo de rota um
valor com base em quão específico ele é. Precedência do modelo de rota:

Evita a necessidade de ajustar a ordem dos pontos de extremidade em casos


comuns.
Tenta corresponder às expectativas de bom senso do comportamento de
roteamento.

Por exemplo, considere modelos /Products/List e /Products/{id} . Seria razoável supor


que /Products/List é uma correspondência melhor do que /Products/{id} para o
caminho /Products/List da URL . Isso funciona porque o segmento /List literal é
considerado com precedência melhor do que o segmento /{id} de parâmetro .

Os detalhes de como a precedência funciona são acoplados à forma como os modelos


de rota são definidos:

Modelos com mais segmentos são considerados mais específicos.


Um segmento com texto literal é considerado mais específico do que um
segmento de parâmetro.
Um segmento de parâmetro com uma restrição é considerado mais específico do
que um sem.
Um segmento complexo é considerado como específico como um segmento de
parâmetro com uma restrição.
Os parâmetros catch-all são os menos específicos. Confira catch-all na seção
Modelos de rota para obter informações importantes sobre rotas catch-all.

Conceitos de geração de URL


Geração de URL:

É o processo pelo qual o roteamento pode criar um caminho de URL com base em
um conjunto de valores de rota.
Permite uma separação lógica entre os pontos de extremidade e as URLs que os
acessam.

O roteamento de ponto de extremidade inclui a LinkGenerator API. LinkGenerator é um


serviço singleton disponível na DI. A LinkGenerator API pode ser usada fora do contexto
de uma solicitação em execução. Mvc.IUrlHelper e cenários que dependem IUrlHelperde
, como Auxiliares de Marca, Auxiliares HTML e Resultados de Ação, usam a
LinkGenerator API internamente para fornecer recursos de geração de link.

O gerador de link é respaldado pelo conceito de um endereço e esquemas de


endereço. Um esquema de endereço é uma maneira de determinar os pontos de
extremidade que devem ser considerados para a geração de link. Por exemplo, os
cenários de nomes de rota e valores de rota com os quais muitos usuários estão
familiarizados com controladores e Razor Pages são implementados como um esquema
de endereços.

O gerador de link pode vincular a controladores e Razor Páginas por meio dos seguintes
métodos de extensão:

GetPathByAction
GetUriByAction
GetPathByPage
GetUriByPage

As sobrecargas desses métodos aceitam argumentos que incluem o HttpContext . Esses


métodos são funcionalmente equivalentes a URL.Action e Url.Page, mas oferecem
flexibilidade e opções adicionais.

Os GetPath* métodos 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
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 de 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:

Método de Descrição
extensão

GetPathByAddress Gera um URI com um caminho absoluto com base nos valores
fornecidos.

GetUriByAddress Gera um URI absoluto com base nos valores fornecidos.

2 Aviso

Preste atenção às seguintes implicações da chamada de métodos LinkGenerator:

Use métodos de extensão de GetUri* com cuidado em uma configuração de


aplicativo que não valide o cabeçalho Host das solicitações de entrada. Se o
Host cabeçalho das solicitações de entrada não for validado, a entrada de

solicitação não confiável poderá ser enviada de volta para o cliente em URIs
em uma exibição ou página. Recomendamos que todos os aplicativos de
produção configurem seu servidor para validar o cabeçalho Host com os
valores válidos conhecidos.

Use LinkGenerator com cuidado no middleware em combinação com Map ou


MapWhen . Map* altera o caminho base da solicitação em execução, o que afeta

a saída da geração de link. Todas as APIs de LinkGenerator permitem a


especificação de um caminho base. Especifique um caminho base vazio para
desfazer o Map* efeito na geração de link.

Exemplo de middleware
No exemplo a seguir, um middleware usa a LinkGenerator API para criar um link para
um método de ação que lista os produtos de armazenamento. O uso do gerador de link
injetando-o em uma classe e chamando GenerateLink está disponível para qualquer
classe em um aplicativo:

C#

public class ProductsMiddleware


{
private readonly LinkGenerator _linkGenerator;

public ProductsMiddleware(RequestDelegate next, LinkGenerator


linkGenerator) =>
_linkGenerator = linkGenerator;

public async Task InvokeAsync(HttpContext httpContext)


{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

var productsPath = _linkGenerator.GetPathByAction("Products",


"Store");

await httpContext.Response.WriteAsync(
$"Go to {productsPath} to see our products.");
}
}

Modelos de rota
Os tokens dentro {} definem parâmetros de rota associados se a rota for
correspondida. Mais de um parâmetro de rota pode ser definido em um segmento de
rota, mas os parâmetros de rota devem ser separados por um valor literal. Por exemplo,
{controller=Home}{action=Index} não é uma rota válida, já que não há nenhum valor
literal entre {controller} e {action} . Os parâmetros de rota devem ter um nome e
podem ter atributos adicionais especificados.

Um texto literal diferente dos parâmetros de rota (por exemplo, {id} ) e do separador
de caminho / precisa corresponder ao texto na URL. A correspondência de texto não
diferencia maiúsculas de minúsculas e se baseia na representação decodificada do
caminho da URL. Para corresponder a um delimitador { de parâmetro de rota literal ou
} , escape do delimitador repetindo o caractere. Por exemplo {{ , ou }} .

Asterisco * ou asterisco ** duplo:

Pode ser usado como um prefixo para um parâmetro de rota a ser associado ao
restante do URI.
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 após
ele.
O valor a seguir blog/ é atribuído ao valor da rota de lesma .

Os parâmetros catch-all também podem corresponder à cadeia de caracteres vazia.

O parâmetro catch-all escapa dos caracteres apropriados quando a rota é usada para
gerar uma URL, incluindo 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 .

Padrões de URL que tentam capturar um nome de arquivo com uma extensão de
arquivo opcional apresentam considerações adicionais. Por exemplo, considere o
modelo files/{filename}.{ext?} . Quando existem valores para filename e ext , ambos
os valores são populados. Se apenas um valor para filename existir na URL, a rota
corresponderá porque o à . direita é opcional. As URLs a seguir correspondem a essa
rota:

/files/myFile.txt

/files/myFile

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 são opcionais acrescentando um ponto de interrogação ( ? ) ao final do nome do
parâmetro. Por exemplo, id? . A diferença entre valores opcionais e parâmetros de rota
padrão é:

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. Adicionar : e o 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 acrescentando outro : nome de restrição
e.

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 restrições de rota e uma lista das restrições fornecidas pela estrutura, consulte a
seção Restrições de rota.

Os parâmetros de rota também podem ter transformadores de parâmetro.


Transformadores de parâmetro transformam o valor de um parâmetro ao gerar links e
ações e páginas correspondentes para URLs. Assim como as restrições, os
transformadores de parâmetro podem ser adicionados embutidos a um parâmetro de
rota adicionando um : nome de transformador e 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, consulte a
seção Transformadores de parâmetro .

A tabela a seguir demonstra modelos de rota de exemplo e seu comportamento:

Modelo de rota URI de O URI da solicitação...


correspondência de
exemplo

hello /hello Somente corresponde ao


caminho único /hello .

{Page=Home} / Faz a correspondência e


define Page como Home .

{Page=Home} /Contact Faz a correspondência e


define Page como Contact .

{controller}/{action}/{id?} /Products/List É mapeado para o


controlador Products e a
ação List .
Modelo de rota URI de O URI da solicitação...
correspondência de
exemplo

{controller}/{action}/{id?} /Products/Details/123 Mapeia para o Products


controlador e Details a
ação com definido como id
123.

{controller=Home}/{action=Index}/{id?} / Mapeia para o controlador e


Index o Home método . id é
ignorado.

{controller=Home}/{action=Index}/{id?} /Products Mapeia para o controlador e


Index o Products método .
id é ignorado.

Em geral, o uso de um modelo é a abordagem mais simples para o roteamento.


Restrições e padrões também podem ser especificados fora do modelo de rota.

Segmentos complexos
Segmentos complexos são processados pela correspondência de delimitadores literais
da direita para a esquerda de maneira não greedy . Por exemplo, [Route("/a{b}c{d}")]
é um segmento complexo. Segmentos complexos funcionam de uma maneira específica
que deve ser compreendida para usá-los com êxito. O exemplo nesta seção demonstra
por que segmentos complexos só funcionam bem quando o texto delimitador não
aparece dentro dos valores de parâmetro. Usar um regex e, em seguida, extrair
manualmente os valores é necessário para casos mais complexos.

2 Aviso

Ao usar System.Text.RegularExpressions para processar entradas não confiáveis,


passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada para
RegularExpressions , causando um ataque de negação de serviço . APIs ASP.NET
Core Framework que usam RegularExpressions passam um tempo limite.

Este é um resumo das etapas que o roteamento executa com o modelo /a{b}c{d} e o
caminho /abcd da URL . O | é usado para ajudar a visualizar como o algoritmo
funciona:

O primeiro literal, da direita para a esquerda, é c . Portanto, /abcd é pesquisado da


direita e localiza /ab|c|d .
Tudo à direita ( d ) agora corresponde ao parâmetro {d} de rota .
O próximo literal, da direita para a esquerda, é a . Portanto, /ab|c|d é pesquisado
começando de onde paramos e, em seguida a , é encontrado /|a|b|c|d .
O valor à direita ( b ) agora corresponde ao parâmetro {b} de rota .
Não há nenhum texto restante e nenhum modelo de rota restante, portanto, essa
é uma correspondência.

Aqui está um exemplo de um caso negativo usando o mesmo modelo /a{b}c{d} e o


caminho /aabcd da URL . O | é usado para ajudar a visualizar como o algoritmo
funciona. Esse caso não é uma correspondência, que é explicada pelo mesmo algoritmo:

O primeiro literal, da direita para a esquerda, é c . Portanto, /aabcd é pesquisado


da direita e localiza /aab|c|d .
Tudo à direita ( d ) agora corresponde ao parâmetro {d} de rota .
O próximo literal, da direita para a esquerda, é a . Portanto, /aab|c|d é pesquisado
começando de onde paramos e, em seguida a , é encontrado /a|a|b|c|d .
O valor à direita ( b ) agora corresponde ao parâmetro {b} de rota .
Neste ponto, há texto a restante, mas o algoritmo ficou sem modelo de rota para
analisar, portanto, isso não é uma correspondência.

Como o algoritmo correspondente não é greedy:

Ele corresponde à menor quantidade de texto possível em cada etapa.


Qualquer caso em que o valor delimitador apareça dentro dos valores de
parâmetro resulta em não correspondência.

Expressões regulares fornecem muito mais controle sobre seu comportamento


correspondente.

A correspondência gananciosa, também conhecida como correspondência lenta ,


corresponde à maior cadeia de caracteres possível. Não greedy corresponde à menor
cadeia de caracteres possível.

Restrições de rota
As restrições de rota são executadas quando ocorre uma correspondência com a URL de
entrada e é criado um token do caminho da URL em valores de rota. As restrições de
rota geralmente inspecionam o valor de rota associado por meio do modelo de rota e
fazem uma decisão verdadeira ou falsa sobre se o valor é aceitável. Algumas restrições
da rota usam dados fora do valor de rota para considerar se a solicitação pode ser
encaminhada. Por exemplo, a HttpMethodRouteConstraint pode aceitar ou rejeitar uma
solicitação de acordo com o verbo HTTP. As restrições são usadas em solicitações de
roteamento e na geração de link.

2 Aviso

Não use restrições para a validação de entrada. Se as restrições forem usadas para
validação de entrada, a entrada inválida resultará em uma 404 resposta Não
Encontrada. A entrada inválida deve produzir uma 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.

A tabela a seguir demonstra as restrições de rota de exemplo e seu comportamento


esperado:

restrição Exemplo Correspondências Observações


de exemplo

int {id:int} 123456789 , Corresponde a qualquer


-123456789 inteiro

bool {active:bool} true , FALSE Correspondências true ou


false . Não diferenciam
maiúsculas de minúsculas

datetime {dob:datetime} 2016-12-31 , 2016- Corresponde a um valor


12-31 7:32pm válido DateTime na cultura
invariável. Consulte o aviso
anterior.

decimal {price:decimal} 49.99 , -1,000.01 Corresponde a um valor


válido decimal na cultura
invariável. Consulte o aviso
anterior.

double {weight:double} 1.234 , Corresponde a um valor


-1,001.01e8 válido double na cultura
invariável. Consulte o aviso
anterior.

float {weight:float} 1.234 , Corresponde a um valor


-1,001.01e8 válido float na cultura
invariável. Consulte o aviso
anterior.
restrição Exemplo Correspondências Observações
de exemplo

guid {id:guid} CD2C1638-1638- Corresponde a um valor


72D5-1638- Guid válido
DEADBEEF1638

long {ticks:long} 123456789 , Corresponde a um valor


-123456789 long válido

minlength(value) {username:minlength(4)} Rick A cadeia de caracteres


deve ter, no mínimo, 4
caracteres

maxlength(value) {filename:maxlength(8)} MyFile A cadeia de caracteres não


pode ser maior que 8
caracteres

length(length) {filename:length(12)} somefile.txt A cadeia de caracteres


deve ter exatamente 12
caracteres

length(min,max) {filename:length(8,16)} somefile.txt A cadeia de caracteres


deve ter, pelo menos, 8 e
não mais de 16 caracteres

min(value) {age:min(18)} 19 O valor inteiro deve ser,


pelo menos, 18

max(value) {age:max(120)} 91 O valor inteiro não deve


ser maior que 120

range(min,max) {age:range(18,120)} 91 O valor inteiro deve ser,


pelo menos, 18, mas não
maior que 120

alpha {name:alpha} Rick A cadeia de caracteres


deve consistir em um ou
mais caracteres a - z
alfabéticos e não diferencia
maiúsculas de minúsculas.

regex(expression) {ssn:regex(^\\d{{3}}- 123-45-6789 A cadeia de caracteres


\\d{{2}}-\\d{{4}}$)} deve corresponder à
expressão regular. Confira
dicas sobre como definir
uma expressão regular.
restrição Exemplo Correspondências Observações
de exemplo

required {name:required} Rick Usado para impor que um


valor não parâmetro está
presente durante a
geração de URL

2 Aviso

Ao usar System.Text.RegularExpressions para processar entradas não confiáveis,


passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada para
RegularExpressions , causando um ataque de negação de serviço . APIs ASP.NET
Core Framework que usam RegularExpressions passam um tempo limite.

Várias restrições delimitadas por dois-pontos 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:

C#

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

2 Aviso

Restrições de rota que verificam a URL e são convertidas em um tipo CLR sempre
usam a cultura invariável. Por exemplo, conversão para o tipo int CLR ou
DateTime . Essas restrições pressupõem que a URL não é localizável. As restrições de

rota fornecidas pela estrutura não modificam os valores armazenados nos valores
de rota. Todos os valores de rota analisados com base na URL 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 em restrições

2 Aviso
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis,
passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada para
RegularExpressions , causando um ataque de negação de serviço . APIs ASP.NET
Core Framework que usam RegularExpressions passam um tempo limite.

Expressões regulares podem ser especificadas como restrições embutidas usando a


regex(...) restrição de rota. Os métodos na MapControllerRoute família também

aceitam um literal de objeto de restrições. Se esse formulário for usado, os valores de


cadeia de caracteres serão interpretados como expressões regulares.

O código a seguir usa uma restrição regex embutida:

C#

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");

O código a seguir usa um literal de objeto para especificar uma restrição regex:

C#

app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });

A estrutura do ASP.NET Core adiciona RegexOptions.IgnoreCase |


RegexOptions.Compiled | RegexOptions.CultureInvariant ao construtor de expressão

regular. Confira RegexOptions para obter uma descrição desses membros.

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 ^\d{3}-\d{2}-\d{4}$ regular em uma restrição embutida, use uma
das seguintes opções:

Substitua \ os caracteres fornecidos na cadeia de caracteres como \\ caracteres


no arquivo de origem C# para escapar do caractere de escape da \ cadeia de
caracteres.
Literais de cadeia de caracteres verbatim.

Para escapar dos caracteres { delimitadores de parâmetro de roteamento , } , [ , ,


] dobre os caracteres na expressão, por exemplo, {{ , }} , [[ , . ]] A tabela a seguir
mostra uma expressão regular e sua versão de escape:

Expressão regular Expressão regular de 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


e correspondem à posição inicial da cadeia de caracteres. As expressões geralmente
terminam com o $ caractere e correspondem ao final da cadeia de caracteres. Os ^
caracteres e $ garantem que a expressão regular corresponda ao valor inteiro do
parâmetro de rota. Sem os ^ caracteres e $ , a expressão regular corresponde a
qualquer subcadeia de caracteres dentro da cadeia de caracteres, o que geralmente é
indesejável. A tabela a seguir fornece exemplos e explica por que eles correspondem ou
não correspondem:

Expression String Corresponder a Comentário

[a-z]{2} hello Sim A subcadeia de caracteres corresponde

[a-z]{2} 123abc456 Sim A subcadeia de caracteres corresponde

[a-z]{2} mz Sim Corresponde à expressão

[a-z]{2} MZ Sim Não diferencia maiúsculas de minúsculas

^[a-z]{2}$ hello Não Confira ^ e $ acima

^[a-z]{2}$ 123abc456 No Confira ^ e $ acima

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.
Restrições passadas no dicionário de restrições que não correspondem a uma das
restrições conhecidas também são tratadas como expressões regulares. As restrições
passadas em um modelo que não correspondem a uma das restrições conhecidas não
são tratadas como expressões regulares.

Restrições de rota personalizadas


Restrições de rota personalizadas podem ser criadas implementando a IRouteConstraint
interface . A IRouteConstraint interface contém Match, que retorna true se a restrição
é atendida e false , caso contrário, .

Restrições de rota personalizadas raramente são necessárias. Antes de implementar uma


restrição de rota personalizada, considere alternativas, como model binding.

A pasta Restrições de ASP.NET Core fornece bons exemplos de criação de restrições.


Por exemplo, GuidRouteConstraint .

Para usar um personalizado IRouteConstraint , o tipo de restrição de rota deve ser


registrado com o do aplicativo no contêiner de ConstraintMap serviço. O ConstraintMap
é um dicionário que mapeia as chaves de restrição de rota para implementações de
IRouteConstraint que validam essas restrições. Um aplicativo ConstraintMap pode ser

atualizado em Program.cs como parte de uma AddRouting chamada ou configurando


RouteOptions diretamente com builder.Services.Configure<RouteOptions> . Por
exemplo:

C#

builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

A restrição anterior é aplicada no seguinte código:

C#

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}

A implementação de NoZeroesRouteConstraint impede que 0 seja usada em um


parâmetro de rota:

C#

public class NoZeroesRouteConstraint : IRouteConstraint


{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));

public bool Match(


HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}

var routeValueString = Convert.ToString(routeValue,


CultureInfo.InvariantCulture);

if (routeValueString is null)
{
return false;
}

return _regex.IsMatch(routeValueString);
}
}

2 Aviso

Ao usar System.Text.RegularExpressions para processar entradas não confiáveis,


passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada para
RegularExpressions , causando um ataque de negação de serviço . APIs ASP.NET
Core Framework que usam RegularExpressions passam um tempo limite.

O código anterior:

0 Impede no {id} segmento da rota.

É mostrado para fornecer um exemplo básico de implementação de uma restrição


personalizada. Ele não deve ser usado em um aplicativo de produção.

O código a seguir é uma abordagem melhor para impedir que um id contendo um 0


seja processado:

C#

[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}

O código anterior tem as seguintes vantagens em relação à NoZeroesRouteConstraint


abordagem:

Ele não requer uma restrição personalizada.


Ele retorna um erro mais descritivo quando o parâmetro de rota inclui 0 .

Transformadores de parâmetro
Transformadores de parâmetro:

Execute ao gerar um link usando LinkGenerator.


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 .

Considere a seguinte IOutboundParameterTransformer implementação:

C#

public class SlugifyParameterTransformer : IOutboundParameterTransformer


{
public string? TransformOutbound(object? value)
{
if (value is null)
{
return null;
}

return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}
Para usar um transformador de parâmetro em um padrão de rota, configure-o usando
ConstraintMap em Program.cs :

C#

builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

A estrutura ASP.NET Core usa transformadores de parâmetro para transformar o URI em


que um ponto de extremidade é resolvido. Por exemplo, transformadores de parâmetro
transformam os valores de rota usados para corresponder a um area , controller ,
action e page :

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");

Com o modelo de rota anterior, a ação SubscriptionManagementController.GetAll é


correspondida com o URI /subscription-management/get-all . Um transformador de
parâmetro não altera os valores de rota usados para gerar um link. Por exemplo,
Url.Action("GetAll", "SubscriptionManagement") gera /subscription-management/get-

all .

ASP.NET Core fornece convenções de API para usar transformadores de parâmetro com
rotas geradas:

A
Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention
convenção MVC 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.
Razor Pages usa a convenção de PageRouteTransformerConvention API. Essa
convenção aplica um transformador de parâmetro especificado a todas as Páginas
descobertas Razor automaticamente. O transformador de parâmetro transforma os
segmentos de pasta e nome de arquivo das rotas de Razor Páginas. Para obter
mais informações, confira Usar um transformador de parâmetros para personalizar
rotas de página.
Referência de geração de URL
Esta seção contém uma referência para o algoritmo implementado pela geração de URL.
Na prática, os exemplos mais complexos de geração de URL usam controladores ou
Razor Páginas. Consulte roteamento em controladores para obter informações
adicionais.

O processo de geração de URL começa com uma chamada para


LinkGenerator.GetPathByAddress ou um método semelhante. O método é fornecido
com um endereço, um conjunto de valores de rota e, opcionalmente, informações sobre
a solicitação atual de HttpContext .

A primeira etapa é usar o endereço para resolver um conjunto de pontos de


extremidade candidatos usando um IEndpointAddressScheme<TAddress> que
corresponda ao tipo do endereço.

Depois que o conjunto de candidatos é encontrado pelo esquema de endereços, os


pontos de extremidade são ordenados e processados iterativamente até que uma
operação de geração de URL seja bem-sucedida. A geração de URL não verifica
ambiguidades, o primeiro resultado retornado é o resultado final.

Solução de problemas de geração de URL com registro


em log
A primeira etapa na geração de URL de solução de problemas é definir o nível de log de
Microsoft.AspNetCore.Routing como TRACE . LinkGenerator registra muitos detalhes
sobre seu processamento, o que pode ser útil para solucionar problemas.

Confira Referência de geração de URL para obter detalhes sobre a geração de URL.

Endereços
Os endereços são o conceito na geração de URL usado para associar uma chamada ao
gerador de link a um conjunto de pontos de extremidade candidatos.

Os endereços são um conceito extensível que vem com duas implementações por
padrão:

Usando o nome do ponto de extremidade ( string ) como o endereço:


Fornece funcionalidade semelhante ao nome da rota do MVC.
Usa o IEndpointNameMetadata tipo de metadados.
Resolve a cadeia de caracteres fornecida em relação aos metadados de todos os
pontos de extremidade registrados.
Gera uma exceção na inicialização se vários pontos de extremidade usarem o
mesmo nome.
Recomendado para uso de uso geral fora de controladores e Razor Páginas.
Usando valores de rota (RouteValuesAddress) como o endereço:
Fornece funcionalidade semelhante para controladores e Razor geração de URL
herdada do Pages.
Muito complexo para estender e depurar.
Fornece a implementação usada por IUrlHelper , Auxiliares de Marca, Auxiliares
HTML, Resultados da Ação etc.

A função do esquema de endereços é fazer a associação entre o endereço e os pontos


de extremidade correspondentes por critérios arbitrários:

O esquema de nome do ponto de extremidade executa uma pesquisa de


dicionário básica.
O esquema de valores de rota tem um subconjunto mais complexo do algoritmo
set.

Valores ambientes e valores explícitos


Na solicitação atual, o roteamento acessa os valores de rota da solicitação
HttpContext.Request.RouteValues atual. Os valores associados à solicitação atual são
chamados de valores de ambiente. Para fins de clareza, a documentação refere-se aos
valores de rota passados para métodos como valores explícitos.

O exemplo a seguir mostra valores de ambiente e valores explícitos. Ele fornece valores
de ambiente da solicitação atual e valores explícitos:

C#

public class WidgetController : ControllerBase


{
private readonly LinkGenerator _linkGenerator;

public WidgetController(LinkGenerator linkGenerator) =>


_linkGenerator = linkGenerator;

public IActionResult Index()


{
var indexPath = _linkGenerator.GetPathByAction(
HttpContext, values: new { id = 17 })!;

return Content(indexPath);
}

// ...

O código anterior:

Retorna /Widget/Index/17
Obtém LinkGenerator via DI.

O código a seguir fornece apenas valores explícitos e nenhum valor ambiente:

C#

var subscribePath = _linkGenerator.GetPathByAction(


"Subscribe", "Home", new { id = 17 })!;

O método anterior retorna /Home/Subscribe/17

O código a WidgetController seguir no retorna /Widget/Subscribe/17 :

C#

var subscribePath = _linkGenerator.GetPathByAction(


HttpContext, "Subscribe", null, new { id = 17 });

O código a seguir fornece o controlador de valores de ambiente na solicitação atual e


valores explícitos:

C#

public class GadgetController : ControllerBase


{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}

No código anterior:

/Gadget/Edit/17 é retornado.

Url obtém o IUrlHelper.


Action gera uma URL com um caminho absoluto para um método de ação. A URL
contém o nome e route os valores especificados action .

O código a seguir fornece valores de ambiente da solicitação atual e valores explícitos:

C#
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });

// ...
}
}

O código anterior define url como /Edit/17 quando a Página de Edição Razor contém
a seguinte diretiva de página:

@page "{id:int}"

Se a página Editar não contiver o "{id:int}" modelo de rota, url será /Edit?id=17 .

O comportamento do MVC IUrlHelper adiciona uma camada de complexidade além das


regras descritas aqui:

IUrlHelper sempre fornece os valores de rota da solicitação atual como valores de


ambiente.
IUrlHelper.Action sempre copia os valores atuais action e controller de rota
como valores explícitos, a menos que substituídos pelo desenvolvedor.
IUrlHelper.Page sempre copia o valor da rota atual page como um valor explícito, a
menos que seja substituído.
IUrlHelper.Page sempre substitui o valor da rota atual handler com null como

valores explícitos, a menos que seja substituído.

Os usuários geralmente ficam surpresos com os detalhes comportamentais dos valores


ambientes, pois o MVC não parece seguir suas próprias regras. Por motivos históricos e
de compatibilidade, determinados valores de rota, como action , controller , page e
handler têm seu próprio comportamento de caso especial.

A funcionalidade equivalente fornecida por LinkGenerator.GetPathByAction e


LinkGenerator.GetPathByPage duplica essas anomalias de IUrlHelper para

compatibilidade.

Processo de geração de URL


Depois que o conjunto de pontos de extremidade candidatos for encontrado, o
algoritmo de geração de URL:

Processa os pontos de extremidade iterativamente.


Retorna o primeiro resultado bem-sucedido.

A primeira etapa nesse processo é chamada de invalidação de valor de rota. A


invalidação de valor de rota é o processo pelo qual o roteamento decide quais valores
de rota dos valores de ambiente devem ser usados e quais devem ser ignorados. Cada
valor ambiente é considerado e combinado com os valores explícitos ou ignorado.

A melhor maneira de pensar sobre a função dos valores ambientes é que eles tentam
salvar os desenvolvedores de aplicativos digitando, em alguns casos comuns.
Tradicionalmente, os cenários em que os valores ambientes são úteis estão relacionados
ao MVC:

Ao vincular a outra ação no mesmo controlador, o nome do controlador não


precisa ser especificado.
Ao vincular a outro controlador na mesma área, o nome da área não precisa ser
especificado.
Ao vincular ao mesmo método de ação, os valores de rota não precisam ser
especificados.
Ao vincular a outra parte do aplicativo, você não deseja carregar valores de rota
que não têm significado nessa parte do aplicativo.

Chamadas para LinkGenerator ou IUrlHelper que retornam null geralmente são


causadas por não entender a invalidação do valor da rota. Solucione problemas de
invalidação de valor de rota especificando explicitamente mais dos valores de rota para
ver se isso resolve o problema.

A invalidação de valor de rota funciona com a suposição de que o esquema de URL do


aplicativo é hierárquico, com uma hierarquia formada da esquerda para a direita.
Considere o modelo {controller}/{action}/{id?} de rota básica do controlador para
ter uma noção intuitiva de como isso funciona na prática. Uma alteração em um valor
invalida todos os valores de rota que aparecem à direita. Isso reflete a suposição sobre
a hierarquia. Se o aplicativo tiver um valor de ambiente para id e a operação especificar
um valor diferente para o controller :

id não será reutilizado porque {controller} está à esquerda de {id?} .

Alguns exemplos que demonstram esse princípio:

Se os valores explícitos contiverem um valor para id , o valor de ambiente para id


será ignorado. Os valores de ambiente para controller e action podem ser
usados.
Se os valores explícitos contiverem um valor para action , qualquer valor de
ambiente para action será ignorado. Os valores de ambiente para controller
podem ser usados. Se o valor explícito para action for diferente do valor de
ambiente para action , o id valor não será usado. Se o valor explícito para action
for o mesmo que o valor de ambiente para action , o id valor poderá ser usado.
Se os valores explícitos contiverem um valor para controller , qualquer valor de
ambiente para controller será ignorado. Se o valor explícito para controller for
diferente do valor de ambiente para controller , os action valores e id não serão
usados. Se o valor explícito para controller for o mesmo que o valor de ambiente
para controller , os action valores e id poderão ser usados.

Esse processo é ainda mais complicado pela existência de rotas de atributo e rotas
convencionais dedicadas. Rotas convencionais do controlador, como
{controller}/{action}/{id?} especificar uma hierarquia usando parâmetros de rota.

Para rotas convencionais dedicadas e rotas de atributo para controladores e Razor


páginas:

Há uma hierarquia de valores de rota.


Eles não aparecem no modelo.

Para esses casos, a geração de URL define o conceito de valores necessários . Os pontos
de extremidade criados por controladores e Razor Páginas têm valores necessários
especificados que permitem que a invalidação de valor de rota funcione.

O algoritmo de invalidação de valor de rota em detalhes:

Os nomes de valor necessários são combinados com os parâmetros de rota e


processados da esquerda para a direita.
Para cada parâmetro, o valor ambiente e o valor explícito são comparados:
Se o valor ambiente e o valor explícito forem os mesmos, o processo
continuará.
Se o valor ambiente estiver presente e o valor explícito não estiver, o valor
ambiente será usado ao gerar a URL.
Se o valor ambiente não estiver presente e o valor explícito estiver, rejeite o
valor ambiente e todos os valores de ambiente subsequentes.
Se o valor ambiente e o valor explícito estiverem presentes e os dois valores
forem diferentes, rejeite o valor ambiente e todos os valores de ambiente
subsequentes.

Neste ponto, a operação de geração de URL está pronta para avaliar as restrições de
rota. O conjunto de valores aceitos é combinado com os valores padrão do parâmetro,
que é fornecido às restrições. Se todas as restrições forem aprovadas, a operação
continuará.
Em seguida, os valores aceitos podem ser usados para expandir o modelo de rota. O
modelo de rota é processado:

Da esquerda para a direita.


Cada parâmetro tem seu valor aceito substituído.
Com os seguintes casos especiais:
Se os valores aceitos não tiverem um valor e o parâmetro tiver um valor padrão,
o valor padrão será usado.
Se os valores aceitos não tiverem um valor e o parâmetro for opcional, o
processamento continuará.
Se qualquer parâmetro de rota à direita de um parâmetro opcional ausente
tiver um valor, a operação falhará.
Parâmetros com valor padrão contíguos e parâmetros opcionais são recolhidos
sempre que possível.

Os valores fornecidos explicitamente que não correspondem a um segmento da rota


são adicionados à cadeia de caracteres de consulta. A tabela a seguir mostra o resultado
do uso do modelo de rota {controller}/{action}/{id?} .

Valores de ambiente Valores explícitos Result

controller = "Home" ação = "About" /Home/About

controller = "Home" controlador = "Order", ação = /Order/About


"About"

controller = "Home", color = ação = "About" /Home/About


"Red"

controller = "Home" ação = "About", cor = "Red" /Home/About?


color=Red

Problemas com a invalidação de valor de rota


O código a seguir mostra um exemplo de um esquema de geração de URL que não é
compatível com o roteamento:

C#

app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });

No código anterior, o culture parâmetro de rota é usado para localização. O desejo é


ter o culture parâmetro sempre aceito como um valor ambiente. No entanto, o
culture parâmetro não é aceito como um valor ambiente devido à maneira como os

valores necessários funcionam:

"default" No modelo de rota, o culture parâmetro de rota é à esquerda de

controller , portanto, as alterações para controller não invalidam culture .

"blog" No modelo de rota, o culture parâmetro de rota é considerado à direita


de controller , que aparece nos valores necessários.

Analisar caminhos de URL com LinkParser


A LinkParser classe adiciona suporte para analisar um caminho de URL em um conjunto
de valores de rota. O ParsePathByEndpointName método usa um nome de ponto de
extremidade e um caminho de URL e retorna um conjunto de valores de rota extraídos
do caminho da URL.

No controlador de exemplo a seguir, a ação GetProduct usa um modelo de rota de


api/Products/{id} e tem um Name de GetProduct :

C#

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...

Na mesma classe de controlador, a ação AddRelatedProduct espera um caminho de URL,


pathToRelatedProduct , que pode ser fornecido como um parâmetro de cadeia de
caracteres de consulta:

C#

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser
linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];

// ...

No exemplo anterior, a ação AddRelatedProduct extrai o id valor da rota do caminho da


URL. Por exemplo, com um caminho de URL de /api/Products/1 , o relatedProductId
valor é definido 1 como . Essa abordagem permite que os clientes da API usem
caminhos de URL ao se referir a recursos, sem exigir conhecimento de como essa URL é
estruturada.

Configurar metadados de ponto de


extremidade
Os links a seguir fornecem informações sobre como configurar metadados de ponto de
extremidade:

Habilitar Cors com roteamento de ponto de extremidade


Exemplo de IAuthorizationPolicyProvider usando um atributo personalizado
[MinimumAgeAuthorize]

Testar a autenticação com o atributo [Authorize]


RequireAuthorization
Selecionando o esquema com o atributo [Authorize]
Aplicar políticas usando o atributo [Authorize]
Autorização baseada em função no ASP.NET Core

Correspondência de host em rotas com


RequireHost
RequireHost aplica uma restrição à rota que requer o host especificado. O RequireHost
parâmetro ou [Host] pode ser um:

Host: www.domain.com , corresponde www.domain.com a qualquer porta.


Host com curinga: *.domain.com , corresponde www.domain.com a ,
subdomain.domain.com ou www.subdomain.domain.com em qualquer porta.

Porta: *:5000 , corresponde à porta 5000 com qualquer host.


Host e porta: www.domain.com:5000 ou *.domain.com:5000 , corresponde ao host e à
porta.
Vários parâmetros podem ser especificados usando RequireHost ou [Host] . A restrição
corresponde aos hosts válidos para qualquer um dos parâmetros. Por exemplo,
[Host("domain.com", "*.domain.com")] corresponde domain.com a , www.domain.com e

subdomain.domain.com .

O código a seguir usa RequireHost para exigir o host especificado na rota:

C#

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");


app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

O código a seguir usa o [Host] atributo no controlador para exigir qualquer um dos
hosts especificados:

C#

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();

[Host("example.com")]
public IActionResult Example() =>
View();
}

Quando o [Host] atributo é aplicado ao controlador e ao método de ação:

O atributo na ação é usado.


O atributo do controlador é ignorado.

Grupos de rotas
O MapGroup método de extensão ajuda a organizar grupos de pontos de extremidade
com um prefixo comum. Ele reduz o código repetitivo e permite personalizar grupos
inteiros de pontos de extremidade com uma única chamada para métodos como
RequireAuthorization e WithMetadata que adicionam metadados de ponto de
extremidade.

Por exemplo, o código a seguir cria dois grupos semelhantes de pontos de extremidade:
C#

app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");

app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();

EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext
factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;

foreach (var argument in factoryContext.MethodInfo.GetParameters())


{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}

// Skip filter if the method doesn't have a TodoDb parameter.


if (dbContextIndex < 0)
{
return next;
}

return async invocationContext =>


{
var dbContext = invocationContext.GetArgument<TodoDb>
(dbContextIndex);
dbContext.IsPrivate = true;

try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise
reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}

C#
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);

return group;
}

Nesse cenário, você pode usar um endereço relativo para o Location cabeçalho no 201
Created resultado:

C#

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb


database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();

return TypedResults.Created($"{todo.Id}", todo);


}

O primeiro grupo de pontos de extremidade corresponderá apenas às solicitações


prefixadas com /public/todos e estarão acessíveis sem nenhuma autenticação. O
segundo grupo de pontos de extremidade corresponderá apenas às solicitações
prefixadas com /private/todos e exigirá autenticação.

A QueryPrivateTodos fábrica de filtros de ponto de extremidade é uma função local que


modifica os parâmetros do manipulador de TodoDb rotas para permitir o acesso e o
armazenamento de dados de tarefas pendentes privados.

Os grupos de rotas também dão suporte a grupos aninhados e padrões de prefixo


complexos com parâmetros de rota e restrições. No exemplo a seguir, o manipulador de
rotas mapeado para o user grupo pode capturar os {org} parâmetros de rota e
{group} definidos nos prefixos do grupo externo.

O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou
filtros de ponto de extremidade a um grupo de pontos de extremidade sem alterar o
padrão de rota.

C#
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

Adicionar filtros ou metadados a um grupo se comporta da mesma maneira que


adicioná-los individualmente a cada ponto de extremidade antes de adicionar filtros ou
metadados extras que possam ter sido adicionados a um grupo interno ou ponto de
extremidade específico.

C#

var outer = app.MapGroup("/outer");


var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/inner group filter");
return next(context);
});

outer.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/outer group filter");
return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("MapGet filter");
return next(context);
});

No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro


interno, mesmo que tenha sido adicionado em segundo lugar. Como os filtros foram
aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos
outros não importa. Os filtros de pedido são adicionados se forem aplicados ao mesmo
grupo ou ponto de extremidade específico.

Uma solicitação para /outer/inner/ registrará o seguinte:

CLI do .NET

/outer group filter


/inner group filter
MapGet filter
Diretrizes de desempenho para roteamento
Quando um aplicativo tem problemas de desempenho, o roteamento geralmente é
suspeito como o problema. O motivo pelo qual o roteamento é suspeito é que
estruturas como controladores e Razor Páginas relatam a quantidade de tempo gasto
dentro da estrutura em suas mensagens de registro em log. Quando há uma diferença
significativa entre o tempo relatado pelos controladores e o tempo total da solicitação:

Os desenvolvedores eliminam o código do aplicativo como a origem do problema.


É comum assumir que o roteamento é a causa.

O roteamento é testado pelo desempenho usando milhares de pontos de extremidade.


É improvável que um aplicativo típico encontre um problema de desempenho apenas
por ser muito grande. A causa raiz mais comum do desempenho de roteamento lento
geralmente é um middleware personalizado mal comportado.

Este exemplo de código a seguir demonstra uma técnica básica para restringir a origem
do atraso:

C#

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>


{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();

logger.LogInformation("Time 1: {ElapsedMilliseconds}ms",
stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>


{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();

logger.LogInformation("Time 2: {ElapsedMilliseconds}ms",
stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>


{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();

logger.LogInformation("Time 3: {ElapsedMilliseconds}ms",
stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

Para roteamento de tempo:

Intercalar cada middleware com uma cópia do middleware de tempo mostrado no


código anterior.
Adicione um identificador exclusivo para correlacionar os dados de tempo com o
código.

Essa é uma maneira básica de restringir o atraso quando ele é significativo, por
exemplo, mais do que 10ms . Subtrair Time 2 de Time 1 relatórios o tempo gasto dentro
do UseRouting middleware.

O código a seguir usa uma abordagem mais compacta para o código de tempo
anterior:

C#

public sealed class AutoStopwatch : IDisposable


{
private readonly ILogger _logger;
private readonly string _message;
private readonly Stopwatch _stopwatch;
private bool _disposed;

public AutoStopwatch(ILogger logger, string message) =>


(_logger, _message, _stopwatch) = (logger, message,
Stopwatch.StartNew());

public void Dispose()


{
if (_disposed)
{
return;
}

_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);

_disposed = true;
}
}
C#

var logger = app.Services.GetRequiredService<ILogger<Program>>();


var timerCount = 0;

app.Use(async (context, next) =>


{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});

app.UseRouting();

app.Use(async (context, next) =>


{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});

app.UseAuthorization();

app.Use(async (context, next) =>


{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});

app.MapGet("/", () => "Timing Test.");

Recursos de roteamento potencialmente caros


A lista a seguir fornece algumas informações sobre recursos de roteamento
relativamente caros em comparação com modelos de rota básicos:

Expressões regulares: é possível escrever expressões regulares complexas ou que


tenham um tempo de execução longo com uma pequena quantidade de entrada.
Segmentos complexos ( {x}-{y}-{z} ):
São significativamente mais caros do que analisar um segmento de caminho de
URL regular.
Resulta na alocação de muitas outras subcadeias de caracteres.
Acesso a dados síncronos: muitos aplicativos complexos têm acesso ao banco de
dados como parte de seu roteamento. Use pontos de extensibilidade como
MatcherPolicy e EndpointSelectorContext, que são assíncronos.
Diretrizes para tabelas de rotas grandes
Por padrão, ASP.NET Core usa um algoritmo de roteamento que troca a memória pelo
tempo de CPU. Isso tem o bom efeito de que o tempo de correspondência de rota
depende apenas do comprimento do caminho a ser correspondido e não do número de
rotas. No entanto, essa abordagem pode ser potencialmente problemática em alguns
casos, quando o aplicativo tem um grande número de rotas (em milhares) e há uma
grande quantidade de prefixos variáveis nas rotas. Por exemplo, se as rotas tiverem
parâmetros nos primeiros segmentos da rota, como {parameter}/some/literal .

É improvável que um aplicativo tenha uma situação em que isso seja um problema, a
menos que:

Há um grande número de rotas no aplicativo usando esse padrão.


Há um grande número de rotas no aplicativo.

Como determinar se um aplicativo está em execução no problema


da tabela de rotas grandes
Há dois sintomas a serem buscadas:
O aplicativo está lento para iniciar na primeira solicitação.
Observe que isso é necessário, mas não suficiente. Há muitos outros
problemas que não são de rota que podem causar uma inicialização lenta do
aplicativo. Verifique a condição abaixo para determinar com precisão se o
aplicativo está em execução nessa situação.
O aplicativo consome muita memória durante a inicialização e um despejo de
memória mostra um grande número de
Microsoft.AspNetCore.Routing.Matching.DfaNode instâncias.

Como resolver esse problema


Há várias técnicas e otimizações que podem ser aplicadas a rotas que melhorarão em
grande parte esse cenário:

Aplique restrições de rota aos parâmetros, por exemplo {parameter:int} , ,


{parameter:guid} , {parameter:regex(\\d+)} etc. sempre que possível.
Isso permite que o algoritmo de roteamento otimize internamente as estruturas
usadas para correspondência e reduza drasticamente a memória usada.
Na grande maioria dos casos, isso será suficiente para voltar a um
comportamento aceitável.
Altere as rotas para mover parâmetros para segmentos posteriores no modelo.
Isso reduz o número de "caminhos" possíveis para corresponder a um ponto de
extremidade dado um caminho.
Use uma rota dinâmica e execute o mapeamento para um controlador/página
dinamicamente.
Isso pode ser obtido usando MapDynamicControllerRoute e MapDynamicPageRoute .

Diretrizes para autores de biblioteca


Esta seção contém diretrizes para criação de autores de biblioteca sobre o roteamento.
Esses detalhes destinam-se a garantir que os desenvolvedores de aplicativos tenham
uma boa experiência usando bibliotecas e estruturas que estendem o roteamento.

Definir pontos de extremidade


Para criar uma estrutura que usa o roteamento para correspondência de URL, comece
definindo uma experiência do usuário que se baseia em UseEndpoints.

FAÇA o build com base IEndpointRouteBuilderem . Isso permite que os usuários


componham sua estrutura com outros recursos ASP.NET Core sem confusão. Cada
modelo de ASP.NET Core inclui roteamento. Suponha que o roteamento esteja presente
e familiar para os usuários.

C#

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

RETORNAR um tipo concreto lacrado de uma chamada para MapMyFramework(...) que


implementa IEndpointConventionBuilder. A maioria dos métodos de estrutura Map...
segue esse padrão. A IEndpointConventionBuilder interface:

Permite que os metadados sejam compostos.


É direcionado por uma variedade de métodos de extensão.

Declarar seu próprio tipo permite que você adicione sua própria funcionalidade
específica da estrutura ao construtor. Não há problema em encapsular um construtor
declarado por estrutura e encaminhar chamadas para ele.

C#
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

CONSIDERE escrever seu próprio EndpointDataSource. EndpointDataSource é o


primitivo de baixo nível para declarar e atualizar uma coleção de pontos de
extremidade. EndpointDataSource é uma API poderosa usada por controladores e Razor
Páginas.

Os testes de roteamento têm um exemplo básico de uma fonte de dados que não
está atualizando.

CONSIDERE a implementação de GetGroupedEndpoints. Isso fornece controle total


sobre a execução de convenções de grupo e os metadados finais nos pontos de
extremidade agrupados. Por exemplo, isso permite que implementações personalizadas
EndpointDataSource executem filtros de ponto de extremidade adicionados a grupos.

NÃO tente registrar um EndpointDataSource por padrão. Exigir que os usuários


registrem sua estrutura no UseEndpoints. A filosofia do roteamento é que nada está
incluído por padrão e esse UseEndpoints é o local para registrar pontos de extremidade.

Criando middleware integrado ao roteamento


CONSIDERE definir tipos de metadados como uma interface.

Faça com que seja possível usar tipos de metadados como um atributo em classes e
métodos.

C#

public interface ICoolMetadata


{
bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Estruturas como controladores e Razor Páginas dão suporte à aplicação de atributos de
metadados a tipos e métodos. Se você declarar tipos de metadados:

Torne-os acessíveis como atributos.


A maioria dos usuários está familiarizada com a aplicação de atributos.

Declarar um tipo de metadados como uma interface adiciona outra camada de


flexibilidade:

As interfaces são redigidas.


Os desenvolvedores podem declarar seus próprios tipos que combinam várias
políticas.

Faça com que seja possível substituir metadados, conforme mostrado no exemplo a
seguir:

C#

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }

[SuppressCoolMetadata]
public void Uncool() { }
}

A melhor maneira de seguir essas diretrizes é evitar a definição de metadados de


marcador:

Não procure apenas a presença de um tipo de metadados.


Defina uma propriedade nos metadados e verifique a propriedade .

A coleção de metadados é ordenada e dá suporte à substituição por prioridade. No


caso dos controladores, os metadados no método de ação são mais específicos.

Torne o middleware útil com e sem roteamento:

C#

app.UseAuthorization(new AuthorizationPolicy() { ... });


// Your framework
app.MapMyFramework(...).RequireAuthorization();

Como exemplo dessa diretriz, considere o UseAuthorization middleware. O middleware


de autorização permite que você passe uma política de fallback. A política de fallback,
se especificada, aplica-se a ambos:

Pontos de extremidade sem uma política especificada.


Solicitações que não correspondem a um ponto de extremidade.

Isso torna o middleware de autorização útil fora do contexto de roteamento. O


middleware de autorização pode ser usado para programação de middleware
tradicional.

Diagnóstico de depuração
Para obter uma saída de diagnóstico de roteamento detalhada, defina
Logging:LogLevel:Microsoft como Debug . No ambiente de desenvolvimento, defina o
nível de log em appsettings.Development.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Tratar erros no ASP.NET Core
Artigo • 28/11/2022 • 52 minutos para o fim da leitura

Por Tom Dykstra

Este artigo aborda abordagens comuns para lidar com erros em ASP.NET Core
aplicativos Web. Consulte Tratar erros em ASP.NET Core APIs Web para APIs Web.

Página de exceção do desenvolvedor


A Página de Exceção do Desenvolvedor exibe informações detalhadas sobre exceções de
solicitação sem tratamento. ASP.NET Core aplicativos habilitam a página de exceção do
desenvolvedor por padrão quando ambos:

Em execução no ambiente de desenvolvimento.


Aplicativo criado com os modelos atuais, ou seja, usando
WebApplication.CreateBuilder. Os aplicativos criados usando o
WebHost.CreateDefaultBuilder devem habilitar a página de exceção do
desenvolvedor chamando app.UseDeveloperExceptionPage em Configure .

A página de exceção do desenvolvedor é executada no início do pipeline de


middleware, para que ela possa capturar exceções sem tratamento geradas no
middleware a seguir.

Informações detalhadas de exceção não devem ser exibidas publicamente quando o


aplicativo é executado no ambiente de Produção. Para obter mais informações sobre
como configurar ambientes, consulte Usar vários ambientes no ASP.NET Core.

A Página de Exceção do Desenvolvedor pode incluir as seguintes informações sobre a


exceção e a solicitação:

Rastreamento de pilha
Parâmetros de cadeia de caracteres de consulta, se houver
Cookies, se houver
Cabeçalhos

Não há garantia de que a Página de Exceção do Desenvolvedor forneça nenhuma


informação. Use o registro em log para obter informações de erro completas.

Página do Manipulador de exceção


Para configurar uma página personalizada de tratamento de erros para o ambiente de
produção, chame UseExceptionHandler. Este middleware de tratamento de exceção:

Captura e registra exceções sem tratamento.


Executa novamente a solicitação em um pipeline alternativo usando o caminho
indicado. A solicitação não será executada novamente se a resposta tiver sido
iniciada. O código gerado pelo modelo executa novamente a solicitação usando o
/Error caminho .

2 Aviso

Se o pipeline alternativo gerar uma exceção própria, o Middleware de Tratamento


de Exceções lançará novamente a exceção original.

No exemplo a seguir, UseExceptionHandler adiciona o middleware de tratamento de


exceção em ambientes que não são de desenvolvimento:

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

O Razor modelo de aplicativo Páginas fornece uma página De erro ( .cshtml ) e


PageModel uma classe ( ErrorModel ) na pasta Páginas . Para um aplicativo MVC, o
modelo de projeto inclui um Error método de ação e uma exibição error para o Home
controlador.

O middleware de tratamento de exceção executa novamente a solicitação usando o


método HTTP original . Se um ponto de extremidade do manipulador de erros for
restrito a um conjunto específico de métodos HTTP, ele será executado somente para
esses métodos HTTP. Por exemplo, uma ação do controlador MVC que usa o [HttpGet]
atributo é executada somente para solicitações GET. Para garantir que todas as
solicitações cheguem à página de tratamento de erros personalizada, não as restrinja a
um conjunto específico de métodos HTTP.

Para lidar com exceções de forma diferente com base no método HTTP original:
Para Razor Páginas, crie vários métodos de manipulador. Por exemplo, use OnGet
para manipular exceções GET e usar OnPost para lidar com exceções POST.
Para MVC, aplique atributos de verbo HTTP a várias ações. Por exemplo, use
[HttpGet] para manipular exceções GET e usar [HttpPost] para lidar com
exceções POST.

Para permitir que usuários não autenticados exibam a página de tratamento de erros
personalizada, verifique se ela dá suporte ao acesso anônimo.

Acessar a exceção
Use IExceptionHandlerPathFeature para acessar a exceção e o caminho da solicitação
original em um manipulador de erros. O exemplo a seguir usa
IExceptionHandlerPathFeature para obter mais informações sobre a exceção que foi

gerada:

C#

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


= true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

public string? ExceptionMessage { get; set; }

public void OnGet()


{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();

if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "The file was not found.";
}

if (exceptionHandlerPathFeature?.Path == "/")
{
ExceptionMessage ??= string.Empty;
ExceptionMessage += " Page: Home.";
}
}
}
2 Aviso

Não forneça informações de erro confidenciais aos clientes. Fornecer erros é um


risco à segurança.

Lambda do Manipulador de exceção


Uma alternativa a uma página personalizada de manipulador de exceção é fornecer um
lambda a UseExceptionHandler. Usar um lambda permite acessar o erro antes de
retornar a resposta.

O código a seguir usa um lambda para tratamento de exceções:

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode =
StatusCodes.Status500InternalServerError;

// using static System.Net.Mime.MediaTypeNames;


context.Response.ContentType = Text.Plain;

await context.Response.WriteAsync("An exception was thrown.");

var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();

if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not
found.");
}

if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});

app.UseHsts();
}
2 Aviso

Não forneça informações de erro confidenciais aos clientes. Fornecer erros é um


risco à segurança.

UseStatusCodePages
Por padrão, um aplicativo ASP.NET Core não fornece uma página de código de status
para códigos de status de erro HTTP, como 404 – Não encontrado. Quando o aplicativo
define um código de status de erro HTTP 400-599 que não tem um corpo, ele retorna o
código de status e um corpo de resposta vazio. Para habilitar manipuladores padrão
somente de texto para códigos de status de erro comuns, chame UseStatusCodePages
em Program.cs :

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePages();

Chame UseStatusCodePages antes do middleware de tratamento de solicitação. Por


exemplo, chame UseStatusCodePages antes do Middleware de Arquivo Estático e do
Middleware de Pontos de Extremidade.

Quando UseStatusCodePages não é usado, navegar até uma URL sem um ponto de
extremidade retorna uma mensagem de erro dependente do navegador indicando que
o ponto de extremidade não pode ser encontrado. Quando UseStatusCodePages é
chamado, o navegador retorna a seguinte resposta:

Console

Status Code: 404; Not Found

UseStatusCodePages normalmente não é usado em produção porque retorna uma

mensagem que não é útil para os usuários.


7 Observação

O middleware de páginas de código de status não captura exceções. Para fornecer


uma página personalizada de tratamento de erros, use a página do manipulador
de exceção.

UseStatusCodePages com cadeia de caracteres de


formato
Para personalizar o texto e o tipo de conteúdo de resposta, use uma sobrecarga de
UseStatusCodePages que leva um tipo de conteúdo e uma cadeia de caracteres de
formato:

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;


app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

No código anterior, {0} é um espaço reservado para o código de erro.

UseStatusCodePages com uma cadeia de caracteres de formato normalmente não é

usada em produção porque retorna uma mensagem que não é útil para os usuários.

UseStatusCodePages com lambda


Para especificar a manipulação de erro personalizada e o código de gravação de
resposta, use a sobrecarga de UseStatusCodePages que leva uma expressão lambda:

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages(async statusCodeContext =>
{
// using static System.Net.Mime.MediaTypeNames;
statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

await statusCodeContext.HttpContext.Response.WriteAsync(
$"Status Code Page:
{statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages com um lambda normalmente não é usado em produção porque


ele retorna uma mensagem que não é útil para os usuários.

UseStatusCodePagesWithRedirects
O método de extensão UseStatusCodePagesWithRedirects:

Envia um código de status 302 – Encontrado ao cliente.


Redireciona o cliente para o ponto de extremidade de tratamento de erros
fornecido no modelo de URL. O ponto de extremidade de tratamento de erros
normalmente exibe informações de erro e retorna HTTP 200.

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

O modelo de URL pode incluir um {0} espaço reservado para o código de status,
conforme mostrado no código anterior. Se o modelo de URL começar com ~ (til), o ~
será substituído pelo do PathBase aplicativo. Ao especificar um ponto de extremidade
no aplicativo, crie uma exibição ou Razor página MVC para o ponto de extremidade.

Este método normalmente é usado quando o aplicativo:

Deveria redirecionar o cliente para um terminal diferente, geralmente em situações


nas quais um aplicativo diferente processa o erro. Para aplicativos Web, a barra de
endereços do navegador do cliente reflete o ponto de extremidade redirecionado.
Não deveria preservar e retornar o código de status original com a resposta de
redirecionamento inicial.

UseStatusCodePagesWithReExecute
O método de extensão UseStatusCodePagesWithReExecute:

Retorna o código de status original ao cliente.


Gera o corpo da resposta ao executar novamente o pipeline de solicitação por
meio de um caminho alternativo.

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Se um ponto de extremidade dentro do aplicativo for especificado, crie uma exibição ou


Razor página MVC para o ponto de extremidade.

Este método normalmente é usado quando o aplicativo tem que:

Processar a solicitação sem redirecionar para um ponto de extremidade diferente.


Para aplicativos Web, a barra de endereços do navegador do cliente reflete o
ponto de extremidade originalmente solicitado.
Preservar e retornar o código de status original com a resposta.

O modelo de URL deve começar com / e pode incluir um espaço reservado {0} para o
código de status. Para passar o código de status como um parâmetro de cadeia de
caracteres de consulta, passe um segundo argumento para
UseStatusCodePagesWithReExecute . Por exemplo:

C#

app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

O ponto de extremidade que processa o erro pode obter a URL original que gerou o
erro, conforme mostrado no exemplo a seguir:
C#

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


= true)]
public class StatusCodeModel : PageModel
{
public int OriginalStatusCode { get; set; }

public string? OriginalPathAndQuery { get; set; }

public void OnGet(int statusCode)


{
OriginalStatusCode = statusCode;

var statusCodeReExecuteFeature =
HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

if (statusCodeReExecuteFeature is not null)


{
OriginalPathAndQuery = string.Join(
statusCodeReExecuteFeature.OriginalPathBase,
statusCodeReExecuteFeature.OriginalPath,
statusCodeReExecuteFeature.OriginalQueryString);
}
}
}

Desabilitar páginas de código de status


Para desabilitar as páginas de código de status de um método de ação ou controlador
MVC, use o atributo [SkipStatusCodePages].

Para desabilitar páginas de código de status para solicitações específicas em um Razor


método de manipulador pages ou em um controlador MVC, use
IStatusCodePagesFeature:

C#

public void OnGet()


{
var statusCodePagesFeature =
HttpContext.Features.Get<IStatusCodePagesFeature>();

if (statusCodePagesFeature is not null)


{
statusCodePagesFeature.Enabled = false;
}
}
Código de tratamento de exceção
O código em páginas de tratamento de exceção também pode gerar exceções. As
páginas de erro de produção devem ser testadas detalhadamente e ter cuidado extra
para evitar gerar exceções próprias.

Cabeçalhos de resposta
Depois que os cabeçalhos de resposta são enviados:

O aplicativo já não consegue alterar o código de status da resposta.


As páginas de exceção ou manipuladores não podem ser executados. A resposta
deve ser concluída ou a conexão será anulada.

Tratamento de exceções do servidor


Além da lógica de tratamento de exceção em um aplicativo, a implementação do
servidor HTTP pode lidar com algumas exceções. Se o servidor capturar uma exceção
antes que os cabeçalhos de resposta sejam enviados, o servidor enviará uma 500 -
Internal Server Error resposta sem um corpo de resposta. Se o servidor capturar uma
exceção depois que os cabeçalhos de resposta forem enviados, o servidor fechará a
conexão. As solicitações que não são tratadas pelo aplicativo são tratadas pelo servidor.
Qualquer exceção que ocorrer quando o servidor estiver tratando a solicitação será
tratada pelo tratamento de exceção do servidor. As páginas de erro personalizadas do
aplicativo, o middleware de tratamento de exceção e os filtros configurados não afetam
esse comportamento.

Tratamento de exceção na inicialização


Apenas a camada de hospedagem pode tratar exceções que ocorrem durante a
inicialização do aplicativo. O host pode ser configurado para capturar erros de
inicialização e capturar erros detalhados.

A camada de hospedagem só poderá mostrar uma página de erro para um erro de


inicialização capturado se o erro ocorrer após a associação de endereço do host/porta.
Se a associação falhar:

A camada de hospedagem registrará uma exceção crítica.


O processo dotnet falhará.
Nenhuma página de erro é exibida quando o servidor HTTP é Kestrel.
Quando executado no IIS (ou no Serviço de Aplicativo do Azure) ou no IIS Express, um
erro 502.5 Falha no Processo será retornado pelo Módulo do ASP.NET Core se o
processo não puder ser iniciado. Para obter mais informações, consulte Solucionar
problemas de ASP.NET Core no Serviço de Aplicativo do Azure e no IIS.

Página de erro do banco de dados


O filtro AddDatabaseDeveloperPageExceptionFilter de exceção de página do
desenvolvedor de banco de dados captura exceções relacionadas ao banco de dados
que podem ser resolvidas usando migrações do Entity Framework Core. Quando essas
exceções ocorrem, uma resposta HTML é gerada com detalhes de possíveis ações para
resolver o problema. Esta página está habilitada apenas no ambiente de
desenvolvimento. O código a seguir adiciona o filtro de exceção da página do
desenvolvedor de banco de dados:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtros de exceção
Em aplicativos MVC, os filtros de exceção podem ser configurados globalmente, por
controlador ou por ação. Em Razor aplicativos Pages, eles podem ser configurados
globalmente ou por modelo de página. Esses filtros lidam com exceções sem
tratamento que ocorrem durante a execução de uma ação do controlador ou de outro
filtro. Para obter mais informações, consulte Filtros em ASP.NET Core.

Os filtros de exceção são úteis para interceptar exceções que ocorrem em ações do
MVC, mas não são tão flexíveis quanto o middleware interno de tratamento de
exceção , . UseExceptionHandler É recomendável usar UseExceptionHandler , a menos
que você precise executar o tratamento de erros de forma diferente com base na ação
do MVC escolhida.

Erros de estado do modelo


Confira informações sobre como lidar com erros de estado de modelo em model
binding e Validação de modelos.
Detalhes do problema
Os Detalhes do Problema não são o único formato de resposta a descrever um erro
de API HTTP, no entanto, eles geralmente são usados para relatar erros para APIs HTTP.

O serviço de detalhes do problema implementa a IProblemDetailsService interface , que


dá suporte à criação de detalhes do problema em ASP.NET Core. O AddProblemDetails
método de extensão no IServiceCollection registra a implementação padrão
IProblemDetailsService .

Em aplicativos ASP.NET Core, o middleware a seguir gera detalhes do problema


respostas HTTP quando AddProblemDetails é chamado, exceto quando o Accept
cabeçalho HTTP da solicitação não inclui um dos tipos de conteúdo com suporte pelo
registrado IProblemDetailsWriter (padrão: application/json ):

ExceptionHandlerMiddleware: gera uma resposta de detalhes do problema


quando um manipulador personalizado não é definido.
StatusCodePagesMiddleware: gera uma resposta de detalhes do problema por
padrão.
DeveloperExceptionPageMiddleware: gera uma resposta de detalhes do problema
no desenvolvimento quando o cabeçalho HTTP da Accept solicitação não inclui
text/html .

O código a seguir configura o aplicativo para gerar uma resposta de detalhes do


problema para todas as respostas de erro de cliente e servidor HTTP que ainda não têm
um conteúdo corporal:

C#

var app = builder.Build();

builder.Services.AddProblemDetails();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}

app.UseStatusCodePages();

A próxima seção mostra como personalizar o corpo da resposta de detalhes do


problema.
Personalizar detalhes do problema
A criação automática de um ProblemDetails pode ser personalizada usando as
seguintes opções:

1. Use ProblemDetailsOptions.CustomizeProblemDetails.
2. Usar um personalizado IProblemDetailsWriter
3. Chamar o IProblemDetailsService em um middleware

Operação CustomizeProblemDetails
Os detalhes do problema gerado podem ser personalizados usando
CustomizeProblemDetailse as personalizações são aplicadas a todos os detalhes do
problema gerados automaticamente.

O código a seguir usa ProblemDetailsOptions para definir CustomizeProblemDetails:

C#

var app = builder.Build();

builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails = ctx =>
ctx.ProblemDetails.Extensions.Add("nodeId",
Environment.MachineName));

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}

app.UseStatusCodePages();

Por exemplo, um HTTP Status 400 Bad Request resultado de ponto de extremidade
produz o seguinte corpo de resposta de detalhes do problema:

JSON

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Bad Request",
"status": 400,
"nodeId": "my-machine-name"
}
Personalizado IProblemDetailsWriter

Uma IProblemDetailsWriter implementação pode ser criada para personalizações


avançadas:

C#

public class SampleProblemDetailsWriter : IProblemDetailsWriter


{
// Indicates that only responses with StatusCode == 400
// are handled by this writer. All others are
// handled by different registered writers if available.
public bool CanWrite(ProblemDetailsContext context)
=> context.HttpContext.Response.StatusCode == 400;

public ValueTask WriteAsync(ProblemDetailsContext context)


{
// Additional customizations.

// Write to the response.


var response = context.HttpContext.Response;
return new
ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
}
}

Detalhes do problema do Middleware

Uma abordagem alternativa para usar ProblemDetailsOptions com


CustomizeProblemDetails é definir o ProblemDetails no middleware. Uma resposta de
detalhes do problema pode ser gravada chamando IProblemDetailsService.WriteAsync:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.


app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsService>() is
{ }
problemDetailsService)
{
(string Detail, string Type) details =
mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero is
not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://en.wikipedia.org/wiki/Square_root")
};

await problemDetailsService.WriteAsync(new ProblemDetailsContext


{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double
denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature { MathError =

MathErrorType.DivisionByZeroError };
context.Features.Set(errorType);
return Results.BadRequest();
}

return Results.Ok(numerator / denominator);


});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature { MathError =

MathErrorType.NegativeRadicandError };
context.Features.Set(errorType);
return Results.BadRequest();
}

return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

No código anterior, os pontos de extremidade mínimos /divide de API e /squareroot


retornam a resposta de problema personalizada esperada na entrada de erro.

Os pontos de extremidade do controlador de API retornam a resposta de problema


padrão na entrada de erro, não a resposta de problema personalizada. A resposta de
problema padrão é retornada porque o controlador de API gravou no fluxo de resposta,
Detalhes do problema para códigos de status de erro, antes
IProblemDetailsService.WriteAsync de ser chamado e a resposta não é gravada
novamente.

O seguinte ValuesController retorna BadRequestResult, que grava no fluxo de resposta


e, portanto, impede que a resposta de problema personalizada seja retornada.

C#

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
// /api/values/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
HttpContext.Features.Set(errorType);
return BadRequest();
}

return Ok(Numerator / Denominator);


}

// /api/values/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return BadRequest();
}
return Ok(Math.Sqrt(radicand));
}

O seguinte Values3Controller retorna ControllerBase.Problem para que o resultado do


problema personalizado esperado seja retornado:

C#

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
// /api/values3/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
HttpContext.Features.Set(errorType);
return Problem(
title: "Bad Input",
detail: "Divison by zero is not defined.",
type: "https://en.wikipedia.org/wiki/Division_by_zero",
statusCode: StatusCodes.Status400BadRequest
);
}

return Ok(Numerator / Denominator);


}

// /api/values3/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return Problem(
title: "Bad Input",
detail: "Negative or complex numbers are not valid input.",
type: "https://en.wikipedia.org/wiki/Square_root",
statusCode: StatusCodes.Status400BadRequest
);
}

return Ok(Math.Sqrt(radicand));
}

Produzir uma carga ProblemDetails para


exceções
Considere o seguinte aplicativo:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

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

app.MapControllers();
app.Run();

Em ambientes de não desenvolvimento, quando ocorre uma exceção, a seguir está uma
resposta Padrão ProblemDetails que é retornada ao cliente:

JSON

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

Para a maioria dos aplicativos, o código anterior é tudo o que é necessário para
exceções, no entanto, a seção a seguir mostra como obter respostas de problema mais
detalhadas.

Uma alternativa a uma página personalizada de manipulador de exceção é fornecer um


lambda a UseExceptionHandler. O uso de um lambda permite o acesso ao erro e a
gravação de uma resposta de detalhes do problema com
IProblemDetailsService.WriteAsync:

C#

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode =
StatusCodes.Status500InternalServerError;
context.Response.ContentType = Text.Plain;

var title = "Bad Input";


var detail = "Invalid input";
var type = "https://errors.example.com/badInput";

if (context.RequestServices.GetService<IProblemDetailsService>()
is
{ } problemDetailsService)
{
var exceptionHandlerFeature =
context.Features.Get<IExceptionHandlerFeature>();
var exceptionType = exceptionHandlerFeature?.Error;
if (exceptionType != null &&
exceptionType.Message.Contains("infinity"))
{
title = "Arguement exception";
detail = "Invalid input";
type = "https://errors.example.com/arguementException";
}

await problemDetailsService.WriteAsync(new
ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = title,
Detail = detail,
Type = type
}
});
}
});
});
}

app.MapControllers();
app.Run();

2 Aviso

Não forneça informações de erro confidenciais aos clientes. Fornecer erros é um


risco à segurança.

Uma abordagem alternativa para gerar detalhes do problema é usar o pacote Nuget de
terceiros Hellang.Middleware.ProblemDetails que pode ser usado para mapear
exceções e erros do cliente para detalhes do problema.

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Solucionar problemas do ASP.NET Core no Serviço de Aplicativo do Azure e no IIS
Solucionar problemas com erros comuns no Serviço de Aplicativo do Azure e no
IIS com o ASP.NET Core
Fazer solicitações HTTP usando
IHttpClientFactory no ASP.NET Core
Artigo • 10/01/2023 • 72 minutos para o fim da leitura

Por Kirk Larkin , Steve Gordon , Glenn Condron e Ryan Nowak .

É possível registrar e usar um IHttpClientFactory para configurar e criar instâncias de


HttpClient em um aplicativo. IHttpClientFactory oferece os seguintes benefícios:

Fornece um local central para nomear e configurar instâncias lógicas de


HttpClient . Por exemplo, um cliente chamado github pode ser registrado e

configurado para acessar o GitHub . Um cliente padrão pode ser registrado para
acesso geral.
Codifica o conceito de middleware de saída por meio da delegação de
manipuladores em HttpClient . Fornece extensões para o middleware baseado em
Polly aproveitar a delegação de manipuladores no HttpClient .
Gerencia o pooling e o tempo de vida das instâncias HttpClientMessageHandler
subjacentes. O gerenciamento automático evita problemas comuns de DNS
(Sistema de Nomes de Domínio) que ocorrem ao gerenciar manualmente os
tempos de HttpClient vida.
Adiciona uma experiência de registro em log configurável (via ILogger ) para todas
as solicitações enviadas por meio de clientes criados pelo alocador.

O código de exemplo nesta versão do tópico usa System.Text.Json para desserializar o


JSconteúdo ON retornado em respostas HTTP. Para exemplos que usam Json.NET e
ReadAsAsync<T> , use o seletor de versão para selecionar uma versão 2.x deste tópico.

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

A melhor abordagem depende dos requisitos do aplicativo.

Uso básico
Registre-se IHttpClientFactory chamando AddHttpClient em Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddHttpClient();

Um IHttpClientFactory pode ser solicitado usando DI (injeção de dependência). O


código a seguir usa IHttpClientFactory para criar uma instância HttpClient .

C#

public class BasicModel : PageModel


{
private readonly IHttpClientFactory _httpClientFactory;

public BasicModel(IHttpClientFactory httpClientFactory) =>


_httpClientFactory = httpClientFactory;

public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

public async Task OnGet()


{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};

var httpClient = _httpClientFactory.CreateClient();


var httpResponseMessage = await
httpClient.SendAsync(httpRequestMessage);

if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();

GitHubBranches = await JsonSerializer.DeserializeAsync


<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
Usar IHttpClientFactory como no exemplo anterior é uma boa maneira de refatorar um
aplicativo existente. Não há nenhum impacto na maneira em que HttpClient é usado.
Em locais em que as instâncias HttpClient são criadas em um aplicativo existente,
substitua essas ocorrências por chamadas para CreateClient.

Clientes nomeados
Clientes nomeados são uma boa opção quando:

O aplicativo requer muitos usos distintos de HttpClient .


Muitos HttpClient têm configuração diferente.

Especifique a configuração para um nomeado HttpClient durante seu registro em


Program.cs :

C#

builder.Services.AddHttpClient("GitHub", httpClient =>


{
httpClient.BaseAddress = new Uri("https://api.github.com/");

// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});

No código anterior, o cliente é configurado com:

O endereço básico https://api.github.com/ .


Dois cabeçalhos necessários para trabalhar com a API do GitHub.

CreateClient
Cada vez que CreateClient é chamado:

Uma nova instância de HttpClient é criada.


A ação de configuração é chamada.

Para criar um cliente nomeado, passe o nome dele para CreateClient :

C#
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;

public NamedClientModel(IHttpClientFactory httpClientFactory) =>


_httpClientFactory = httpClientFactory;

public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

public async Task OnGet()


{
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");

if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();

GitHubBranches = await JsonSerializer.DeserializeAsync


<IEnumerable<GitHubBranch>>(contentStream);
}
}
}

No código anterior, a solicitação não precisa especificar um nome do host. O código


pode passar apenas o caminho, pois o endereço básico configurado para o cliente é
usado.

Clientes com tipo


Clientes com tipo:

Fornecem as mesmas funcionalidade que os clientes nomeados sem a necessidade


de usar cadeias de caracteres como chaves.
Fornecem a ajuda do IntelliSense e do compilador durante o consumo de clientes.
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.


Para encapsular toda a lógica que lida com o ponto de extremidade.
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:

C#
public class GitHubService
{
private readonly HttpClient _httpClient;

public GitHubService(HttpClient httpClient)


{
_httpClient = httpClient;

_httpClient.BaseAddress = new Uri("https://api.github.com/");

// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
}

public async Task<IEnumerable<GitHubBranch>?>


GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
"repos/dotnet/AspNetCore.Docs/branches");
}

No código anterior:

A configuração é movida para o cliente tipado.


A instância fornecida HttpClient é armazenada como um campo privado.

Métodos específicos da API podem ser criados que expõem a funcionalidade


HttpClient . Por exemplo, o método encapsula o GetAspNetCoreDocsBranches código

para recuperar os documentos de branches do GitHub.

O código a seguir chama AddHttpClient para Program.cs registrar a GitHubService


classe de cliente tipada:

C#

builder.Services.AddHttpClient<GitHubService>();

O cliente com tipo é registrado como transitório com a DI. No código anterior,
AddHttpClient registra GitHubService como um serviço transitório. Esse registro usa um

método de fábrica para:

1. Crie uma instância de HttpClient .


2. Crie uma instância de GitHubService , passando a instância de HttpClient para seu
construtor.
O cliente com tipo pode ser injetado e consumido diretamente:

C#

public class TypedClientModel : PageModel


{
private readonly GitHubService _gitHubService;

public TypedClientModel(GitHubService gitHubService) =>


_gitHubService = gitHubService;

public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

public async Task OnGet()


{
try
{
GitHubBranches = await
_gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}

A configuração de um cliente tipado também pode ser especificada durante seu registro
no , em Program.cs vez de no construtor do cliente digitado:

C#

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");

// ...
});

Clientes gerados
IHttpClientFactory pode ser usado em combinação com bibliotecas de terceiros, como

a Refit . A reajuste é uma REST biblioteca para .NET. Ele converte REST APIs em
interfaces dinâmicas. Chame AddRefitClient para gerar uma implementação dinâmica
de uma interface, que usa para fazer as chamadas HTTP externas HttpClient .

Uma interface personalizada representa a API externa:


C#

public interface IGitHubClient


{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}

Chame AddRefitClient para gerar a implementação dinâmica e, em seguida, chame


ConfigureHttpClient para configurar o subjacente HttpClient :

C#

builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");

// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});

Use a DI para acessar a implementação dinâmica de IGitHubClient :

C#

public class RefitModel : PageModel


{
private readonly IGitHubClient _gitHubClient;

public RefitModel(IGitHubClient gitHubClient) =>


_gitHubClient = gitHubClient;

public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

public async Task OnGet()


{
try
{
GitHubBranches = await
_gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}

Fazer solicitações POST, PUT e DELETE


Nos exemplos anteriores, todas as solicitações HTTP usam o verbo HTTP GET.
HttpClient também dá suporte a outros verbos HTTP, incluindo:

POST
PUT
DELETE
PATCH

Para obter uma lista completa de verbos HTTP compatíveis, consulte HttpMethod.

O exemplo a seguir mostra como fazer uma solicitação HTTP POST:

C#

public async Task CreateItemAsync(TodoItem todoItem)


{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json); // using static System.Net.Mime.MediaTypeNames;

using var httpResponseMessage =


await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

httpResponseMessage.EnsureSuccessStatusCode();
}

No código anterior, o método CreateItemAsync :

Serializa o TodoItem parâmetro para JSON usando System.Text.Json .


Cria uma instância de StringContent para empacotar o ON serializado JSpara
enviar o corpo da solicitação HTTP.
Chama PostAsync para enviar o JSconteúdo ON para a URL especificada. Essa é
uma URL relativa que é adicionada ao HttpClient.BaseAddress.
Chama EnsureSuccessStatusCode para gerar uma exceção se o código de status de
resposta não indicar êxito.

HttpClient também dá suporte a outros tipos de conteúdo. Por exemplo,


MultipartContent e StreamContent. Para obter uma lista completa dos dispositivos com
suporte, consulte HttpContent.
O exemplo a seguir mostra uma solicitação HTTP PUT:

C#

public async Task SaveItemAsync(TodoItem todoItem)


{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json);

using var httpResponseMessage =


await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}",
todoItemJson);

httpResponseMessage.EnsureSuccessStatusCode();
}

O código anterior é semelhante ao exemplo POST. O método SaveItemAsync chama


PutAsync em vez de PostAsync .

O exemplo a seguir mostra uma solicitação HTTP DELETE:

C#

public async Task DeleteItemAsync(long itemId)


{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

httpResponseMessage.EnsureSuccessStatusCode();
}

No código anterior, o método DeleteItemAsync chama DeleteAsync. Como as


solicitações HTTP DELETE normalmente não contêm corpo, o método DeleteAsync não
fornece uma sobrecarga que aceita uma instância de HttpContent .

Para saber mais sobre como usar verbos HTTP diferentes com HttpClient , consulte
HttpClient.

Middleware de solicitação de saída


HttpClient tem o conceito de delegar manipuladores que podem ser vinculados para
solicitações HTTP de saída. IHttpClientFactory :
Simplifica a definição dos manipuladores a serem aplicados a cada cliente
nomeado.
Dá suporte ao registro e ao encadeamento de vários manipuladores para criar um
pipeline de middleware de solicitação de saída. Cada um desses manipuladores é
capaz de executar o trabalho antes e após a solicitação de saída. Esse padrão:
É semelhante ao pipeline de middleware de entrada no ASP.NET Core.
Fornece um mecanismo para gerenciar preocupações transversais em relação a
solicitações HTTP, como:
cache
tratamento de erros
serialização
registro em log

Para criar um manipulador de delegação:

Derivar de DelegatingHandler.
Substitua SendAsync. Execute o código antes de passar a solicitação para o
próximo manipulador no pipeline:

C#

public class ValidateHeaderHandler : DelegatingHandler


{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"The API key header X-API-KEY is required.")
};
}

return await base.SendAsync(request, cancellationToken);


}
}

O código anterior verifica se o X-API-KEY cabeçalho está na solicitação. Se X-API-KEY


estiver ausente, BadRequest será retornado.

Mais de um manipulador pode ser adicionado à configuração de um HttpClient com


Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessage
Handler:
C#

builder.Services.AddTransient<ValidateHeaderHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();

No código anterior, o ValidateHeaderHandler é registrado com a DI. Depois de


registrado, o AddHttpMessageHandler poderá ser chamado, passando o tipo para o
manipulador.

Vários manipuladores podem ser registrados na ordem em que eles devem ser
executados. Cada manipulador encapsula o próximo manipulador até que o
HttpClientHandler final execute a solicitação:

C#

builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();

builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();

No código anterior, SampleHandler1 é executado primeiro, antes SampleHandler2 de .

Usar DI no middleware de solicitação de saída


Quando IHttpClientFactory cria um novo manipulador de delegação, ele usa a DI para
atender aos parâmetros do construtor do manipulador. IHttpClientFactory cria um
escopo de DI separado para cada manipulador, o que pode levar a um comportamento
surpreendente quando um manipulador consome um serviço com escopo .

Por exemplo, considere a seguinte interface e sua implementação, que representa uma
tarefa como uma operação com um identificador, OperationId :

C#

public interface IOperationScoped


{
string OperationId { get; }
}

public class OperationScoped : IOperationScoped


{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

Como o nome sugere, IOperationScoped é registrado com DI usando um tempo de vida


com escopo :

C#

builder.Services.AddScoped<IOperationScoped, OperationScoped>();

O seguinte manipulador de delegação consome e usa IOperationScoped para definir o


X-OPERATION-ID cabeçalho para a solicitação de saída:

C#

public class OperationHandler : DelegatingHandler


{
private readonly IOperationScoped _operationScoped;

public OperationHandler(IOperationScoped operationScoped) =>


_operationScoped = operationScoped;

protected override async Task<HttpResponseMessage> SendAsync(


HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);

return await base.SendAsync(request, cancellationToken);


}
}

HttpRequestsSample No download , navegue até /Operation e atualize a página. O


valor do escopo da solicitação é alterado para cada solicitação, mas o valor do escopo
do manipulador é alterado apenas a cada 5 segundos.

Os manipuladores podem depender de serviços de qualquer escopo. Os serviços dos


quais os manipuladores dependem são descartados quando o manipulador é
descartado.

Use uma das seguintes abordagens para compartilhar o estado por solicitação com os
manipuladores de mensagens:

Passe os dados pelo manipulador usando HttpRequestMessage.Options.


Use IHttpContextAccessor para acessar a solicitação atual.
Crie um objeto de armazenamento AsyncLocal<T> personalizado para passar os
dados.
Usar manipuladores baseados no Polly
IHttpClientFactory integra-se com a biblioteca de terceiros Polly . O Polly é uma
biblioteca abrangente de tratamento de falha transitória e de resiliência para .NET. Ela
permite que os desenvolvedores expressem políticas, como Repetição, Disjuntor, Tempo
Limite, Isolamento de Bulkhead e Fallback de maneira fluente e thread-safe.

Os métodos de extensão são fornecidos para habilitar o uso de políticas do Polly com
instâncias de HttpClient configuradas. As extensões polly dão suporte à adição de
manipuladores baseados em Polly aos clientes. O Polly requer o pacote NuGet
Microsoft.Extensions.Http.Polly .

Tratar falhas transitórias


As falhas normalmente ocorrem quando chamadas HTTP externas são transitórias.
AddTransientHttpErrorPolicy permite que uma política seja definida para lidar com erros
transitórios. As políticas configuradas com lidam com AddTransientHttpErrorPolicy as
seguintes respostas:

HttpRequestException
HTTP 5xx
HTTP 408

AddTransientHttpErrorPolicy fornece acesso a um PolicyBuilder objeto configurado

para lidar com erros que representam uma possível falha transitória:

C#

builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => 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


Métodos de extensão são fornecidos para adicionar manipuladores baseados em Polly,
por exemplo, AddPolicyHandler. A seguinte AddPolicyHandler sobrecarga inspeciona a
solicitação para decidir qual política aplicar:
C#

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(


TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));

builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy :
longTimeoutPolicy);

No código anterior, se a solicitação de saída é um HTTP GET, um tempo limite de 10


segundos é aplicado. Para qualquer outro método HTTP, é usado um tempo limite de
30 segundos.

Adicionar vários manipuladores do Polly


É comum aninhar políticas polly:

C#

builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

No exemplo anterior:

Dois manipuladores são adicionados.


O primeiro manipulador usa AddTransientHttpErrorPolicy para adicionar uma
política de repetição. As solicitações com falha são repetidas até três vezes.
A segunda AddTransientHttpErrorPolicy chamada adiciona uma política de
disjuntor. Outras solicitações externas serão bloqueadas por 30 segundos se 5
tentativas com falha ocorrerem sequencialmente. As políticas de disjuntor são
políticas com estado. Todas as chamadas por meio desse cliente compartilham o
mesmo estado do circuito.

Adicionar políticas do registro do Polly


Uma abordagem para gerenciar as políticas usadas com frequência é defini-las uma
única vez e registrá-las em um PolicyRegistry . Por exemplo:

C#
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));

var policyRegistry = builder.Services.AddPolicyRegistry();

policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);

builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");

builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");

No código anterior:

Duas políticas, Regular e Long , são adicionadas ao registro polly.


AddPolicyHandlerFromRegistry configura clientes nomeados individuais para usar
essas políticas do Registro polly.

Para obter mais informações sobre IHttpClientFactory as integrações do e do Polly,


consulte o wiki do Polly .

HttpClient e gerenciamento de tempo de vida


Uma nova instância de HttpClient é chamada, sempre que CreateClient é chamado na
IHttpClientFactory . Um HttpMessageHandler é criado por cliente nomeado. O

alocador gerencia a vida útil das instâncias do HttpMessageHandler .

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 (Sistema de Nomes de Domínio).

O tempo de vida padrão do manipulador é de 2 minutos. O valor padrão pode ser


substituído por cliente nomeado:
C#

builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));

HttpClient As instâncias geralmente podem ser tratadas como objetos .NET que não
exigem descarte. 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 .

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 .

Alternativas a IHttpClientFactory
Usar IHttpClientFactory em um aplicativo habilitado para DI evita:

Problemas de esgotamento de recursos por instâncias de HttpMessageHandler


pool.
Problemas de DNS obsoletos por meio da circulação HttpMessageHandler de
instâncias em intervalos regulares.

Há maneiras alternativas de resolver os problemas anteriores usando uma instância de


longa duração SocketsHttpHandler .

Crie uma instância de SocketsHttpHandler quando o aplicativo é iniciado e use-o


durante a vida útil do aplicativo.
Configure PooledConnectionLifetime para um valor apropriado com base nos
tempos de atualização de DNS.
Crie HttpClient instâncias usando new HttpClient(handler, disposeHandler:
false) conforme necessário.

As abordagens anteriores resolvem os problemas de gerenciamento de recursos que


IHttpClientFactory resolvem de maneira semelhante.

O SocketsHttpHandler compartilha conexões entre HttpClient instâncias. Esse


compartilhamento impede o esgotamento do soquete.
O SocketsHttpHandler ciclos de conexões de acordo PooledConnectionLifetime
com para evitar problemas de DNS obsoletos.
Log
Os clientes criado pelo IHttpClientFactory registram mensagens de log para todas as
solicitações. Habilite o nível de informações apropriado 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 exemplo MyNamedClient , essas mensagens são registradas com a 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 do envio da solicitação. 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 alterações nos cabeçalhos
de solicitação ou no código de status de resposta.

Incluir o nome do cliente na categoria de log habilita a filtragem de log para clientes
nomeados específicos.

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:

C#

builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});

Cookies
As instâncias em pool resultam HttpMessageHandler em CookieContainer objetos sendo
compartilhados. O compartilhamento do objeto CookieContainer de forma inesperada
geralmente resulta em código incorreto. Para aplicativos que exigem cookies, considere:

Desabilitando o tratamento automático cookie


Evitando IHttpClientFactory

Chame ConfigurePrimaryHttpMessageHandler para desabilitar o tratamento automático


cookie :

C#

builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});

Usar IHttpClientFactory em um aplicativo de


console
Em um aplicativo de console, adicione as seguintes referências de pacote ao projeto:

Microsoft.Extensions.Hosting
Microsoft.Extensions.Http

No exemplo a seguir:

IHttpClientFactory e GitHubService são registrados no contêiner de serviço do


Host Genérico .
GitHubService é solicitado da DI, que, por sua vez, solicita uma instância do

IHttpClientFactory .
GitHubService usa IHttpClientFactory para criar uma instância do HttpClient ,

que usa para recuperar branches do GitHub de documentos.


C#

using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = new HostBuilder()


.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<GitHubService>();
})
.Build();

try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await
gitHubService.GetAspNetCoreDocsBranchesAsync();

Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");

if (gitHubBranches is not null)


{
foreach (var gitHubBranch in gitHubBranches)
{
Console.WriteLine($"- {gitHubBranch.Name}");
}
}
}
catch (Exception ex)
{
host.Services.GetRequiredService<ILogger<Program>>()
.LogError(ex, "Unable to load branches from GitHub.");
}

public class GitHubService


{
private readonly IHttpClientFactory _httpClientFactory;

public GitHubService(IHttpClientFactory httpClientFactory) =>


_httpClientFactory = httpClientFactory;

public async Task<IEnumerable<GitHubBranch>?>


GetAspNetCoreDocsBranchesAsync()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ "Accept", "application/vnd.github.v3+json" },
{ "User-Agent", "HttpRequestsConsoleSample" }
}
};

var httpClient = _httpClientFactory.CreateClient();


var httpResponseMessage = await
httpClient.SendAsync(httpRequestMessage);

httpResponseMessage.EnsureSuccessStatusCode();

using var contentStream =


await httpResponseMessage.Content.ReadAsStreamAsync();

return await JsonSerializer.DeserializeAsync


<IEnumerable<GitHubBranch>>(contentStream);
}
}

public record GitHubBranch(


[property: JsonPropertyName("name")] string Name);

Middleware de propagação de cabeçalho


A propagação de cabeçalho é um middleware ASP.NET Core para propagar cabeçalhos
HTTP da solicitação de entrada para as solicitações de saída HttpClient . Para usar a
propagação de cabeçalho:

Instale o pacote Microsoft.AspNetCore.HeaderPropagation .

Configure o pipeline de HttpClient middleware e no Program.cs :

C#

// Add services to the container.


builder.Services.AddControllers();

builder.Services.AddHttpClient("PropagateHeaders")
.AddHeaderPropagation();

builder.Services.AddHeaderPropagation(options =>
{
options.Headers.Add("X-TraceId");
});

var app = builder.Build();

// Configure the HTTP request pipeline.


app.UseHttpsRedirection();
app.UseHeaderPropagation();

app.MapControllers();

Faça solicitações de saída usando a instância configurada HttpClient , que inclui


os cabeçalhos adicionados.

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Usar HttpClientFactory implementar solicitações HTTP resilientes
Implementar repetições de chamadas HTTP com retirada exponencial com o
HttpClientFactory e políticas da Polly
Implementar o padrão de disjuntor
Como serializar e desserializar JSON no .NET
Arquivos estáticos no ASP.NET Core
Artigo • 10/01/2023 • 24 minutos para o fim da leitura

Por Rick Anderson e Kirk Larkin

Arquivos estáticos, como HTML, CSS, imagens e JavaScript, são ativos que um aplicativo
ASP.NET Core atende diretamente aos clientes por padrão.

Fornecer arquivos estáticos


Os arquivos estáticos são armazenados no diretório raiz da Web do projeto. O diretório
padrão é {content root}/wwwroot , mas pode ser alterado com o UseWebRoot método .
Para obter mais informações, consulte Raiz de conteúdo e raiz da Web.

O método CreateBuilder define a raiz do conteúdo como o diretório atual:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Os arquivos estáticos podem ser acessados por meio de um caminho relativo à raiz da
Web. Por exemplo, os modelos de projeto de Aplicativo Web contêm várias pastas
dentro da wwwroot pasta:

wwwroot
css

js
lib

Considere criar a pasta wwwroot/images e adicionar o wwwroot/images/MyImage.jpg


arquivo. O formato URI para acessar um arquivo na images pasta é
https://<hostname>/images/<image_file_name> . Por exemplo,

https://localhost:5001/images/MyImage.jpg

Fornecer arquivos na raiz da Web


Os modelos de aplicativo Web padrão chamam o UseStaticFiles método em Program.cs ,
que permite que arquivos estáticos sejam atendidos:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

A sobrecarga do método sem UseStaticFiles parâmetros marca os arquivos na raiz da


Web como servíveis. As seguintes referências wwwroot/images/MyImage.jpg de marcação:

HTML

<img src="~/images/MyImage.jpg" class="img" alt="My image" />


Na marcação anterior, o caractere ~ de bloco aponta para a raiz da Web.

Fornecer arquivos fora do diretório base


Considere uma hierarquia de diretório na qual os arquivos estáticos a serem atendidos
residem fora da raiz da Web:

wwwroot
css

images
js

MyStaticFiles

images
red-rose.jpg

Uma solicitação pode acessar o red-rose.jpg arquivo configurando o Middleware de


Arquivo Estático da seguinte maneira:

C#

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

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

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

No código anterior, a hierarquia de diretórios MyStaticFiles é exposta publicamente por


meio do segmento do URI StaticFiles. Uma solicitação para
https://<hostname>/StaticFiles/images/red-rose.jpg atender ao red-rose.jpg arquivo.

As seguintes referências MyStaticFiles/images/red-rose.jpg de marcação:

HTML

<img src="~/StaticFiles/images/red-rose.jpg" class="img" alt="A red rose" />

Para atender arquivos de vários locais, consulte Servir arquivos de vários locais.

Definir cabeçalhos de resposta HTTP


Um StaticFileOptions objeto pode ser usado para definir cabeçalhos de resposta HTTP.
Além de configurar o serviço de arquivo estático da raiz da Web, o seguinte código
define o cabeçalho Cache-Control :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

var cacheMaxAgeOneWeek = (60 * 60 * 24 * 7).ToString();


app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Append(
"Cache-Control", $"public, max-age={cacheMaxAgeOneWeek}");
}
});
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

O código anterior disponibiliza arquivos estáticos publicamente no cache local por uma
semana (604800 segundos).

Autorização de arquivo estático


Os modelos de ASP.NET Core chamam UseStaticFiles antes de chamar UseAuthorization.
A maioria dos aplicativos segue esse padrão. Quando o middleware de arquivo estático
é chamado antes do middleware de autorização:

Nenhuma verificação de autorização é executada nos arquivos estáticos.


Os arquivos estáticos atendidos pelo Middleware de Arquivo Estático, como
aqueles em wwwroot , são publicamente acessíveis.

Para servir arquivos estáticos com base na autorização:

Armazene-os fora de wwwroot .


Chame UseStaticFiles , especificando um caminho, depois de chamar
UseAuthorization .

Defina a política de autorização de fallback.

C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using StaticFileAuth.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

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

app.MapRazorPages();

app.Run();

No código anterior, a política de autorização de fallback exige que todos os usuários


sejam autenticados. Pontos de extremidade como controladores, Razor Páginas etc. que
especificam seus próprios requisitos de autorização não usam a política de autorização
de fallback. Por exemplo, Razor Páginas, controladores ou métodos de ação com
[AllowAnonymous] ou [Authorize(PolicyName="MyPolicy")] usam o atributo de
autorização aplicado em vez da política de autorização de fallback.

RequireAuthenticatedUser adiciona DenyAnonymousAuthorizationRequirement à


instância atual, que impõe que o usuário atual seja autenticado.

Os ativos estáticos em wwwroot são publicamente acessíveis porque o middleware de


arquivo estático padrão ( app.UseStaticFiles(); ) é chamado antes de
UseAuthentication . Os ativos estáticos na pasta MyStaticFiles exigem autenticação. O

código de exemplo demonstra isso.

Uma abordagem alternativa para atender arquivos com base na autorização é:

Armazene-os fora do wwwroot e qualquer diretório acessível para o Middleware de


Arquivo Estático.
Sirva-os por meio de um método de ação ao qual a autorização é aplicada e
retorne um FileResult objeto:

C#

[Authorize]
public class BannerImageModel : PageModel
{
private readonly IWebHostEnvironment _env;

public BannerImageModel(IWebHostEnvironment env) =>


_env = env;

public PhysicalFileResult OnGet()


{
var filePath = Path.Combine(
_env.ContentRootPath, "MyStaticFiles", "images", "red-
rose.jpg");

return PhysicalFile(filePath, "image/jpeg");


}
}

Pesquisa no diretório
A navegação de diretório permite a listagem de diretórios em diretórios especificados.

A navegação de diretório está desabilitada por padrão por motivos de segurança. Para
obter mais informações, consulte Considerações de segurança para arquivos estáticos.

Habilite a navegação de diretório com AddDirectoryBrowser e UseDirectoryBrowser:

C#

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

var fileProvider = new


PhysicalFileProvider(Path.Combine(builder.Environment.WebRootPath,
"images"));
var requestPath = "/MyImages";

// Enable displaying browser links.


app.UseStaticFiles(new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

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


URL https://<hostname>/MyImages , com links para cada arquivo e pasta:
AddDirectoryBrowser adiciona serviços exigidos pelo middleware de navegação de
diretório, incluindo HtmlEncoder. Esses serviços podem ser adicionados por outras
chamadas, como AddRazorPages, mas recomendamos chamar AddDirectoryBrowser
para garantir que os serviços sejam adicionados em todos os aplicativos.

Fornecer documentos padrão


Definir uma página padrão fornece aos visitantes um ponto de partida em um site. Para
servir um arquivo padrão de wwwroot sem exigir que a URL de solicitação inclua o nome
do arquivo, chame o UseDefaultFiles método :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseDefaultFiles();

app.UseStaticFiles();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
UseDefaultFiles deve ser chamado antes de UseStaticFiles para fornecer o arquivo

padrão. UseDefaultFiles é um regravador de URL que não atende ao arquivo.

Com UseDefaultFiles , solicita uma pasta na wwwroot pesquisa por:

default.htm
default.html

index.htm

index.html

O primeiro arquivo encontrado na lista é servido como se a solicitação incluía o nome


do arquivo. A URL do navegador continua refletindo o URI solicitado.

O código a seguir altera o nome do arquivo padrão para mydefault.html :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

var options = new DefaultFilesOptions();


options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);

app.UseStaticFiles();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

UseFileServer para documentos padrão


UseFileServer combina a funcionalidade de UseStaticFiles , UseDefaultFiles e,
opcionalmente UseDirectoryBrowser , .

Chame app.UseFileServer para habilitar o fornecimento de arquivos estáticos e o


arquivo padrão. A navegação de diretório não está habilitada:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseFileServer();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

O código a seguir permite o fornecimento de arquivos estáticos, o arquivo padrão e a


navegação de diretório:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDirectoryBrowser();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();

app.UseFileServer(enableDirectoryBrowsing: true);

app.UseRouting();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Considere a seguinte hierarquia de diretórios:

wwwroot

css

images
js

MyStaticFiles
images

MyImage.jpg

default.html

O código a seguir permite o fornecimento de arquivos estáticos, o arquivo padrão e a


navegação de diretório de MyStaticFiles :

C#

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDirectoryBrowser();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

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

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

AddDirectoryBrowser deve ser chamado quando o valor da EnableDirectoryBrowsing


propriedade é true .

Usando a hierarquia de arquivos e o código anteriores, as URLs são resolvidas da


seguinte maneira:

URI Resposta

https://<hostname>/StaticFiles/images/MyImage.jpg MyStaticFiles/images/MyImage.jpg

https://<hostname>/StaticFiles MyStaticFiles/default.html

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


https://<hostname>/StaticFiles retornará a listagem de diretórios com links clicáveis:
UseDefaultFiles e UseDirectoryBrowser executar um redirecionamento do lado do
cliente do URI de destino sem um URI à direita / para o URI de destino com um à
direita / . Por exemplo, de https://<hostname>/StaticFiles para
https://<hostname>/StaticFiles/ . URLs relativas no diretório StaticFiles são inválidas
sem uma barra à direita ( / ), a menos que a opção RedirectToAppendTrailingSlash de
DefaultFilesOptions seja usada.

FileExtensionContentTypeProvider
A FileExtensionContentTypeProvider classe contém uma Mappings propriedade que
serve como um mapeamento de extensões de arquivo para tipos de conteúdo MIME.
No exemplo a seguir, várias extensões de arquivo são mapeadas para tipos MIME
conhecidos. A extensão .rtf é substituída e .mp4 é removida:

C#

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

// 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
{
ContentTypeProvider = provider
});
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Consulte Tipos de conteúdo MIME .

Tipos de conteúdo não padrão


O Middleware de Arquivo Estático entende quase 400 tipos de conteúdo de arquivo
conhecidos. Se o usuário solicitar um arquivo com um tipo de arquivo desconhecido, o
Middleware de Arquivo Estático passará a solicitação para o próximo middleware no
pipeline. Se nenhum middleware manipular a solicitação, uma resposta 404 Não
Encontrado será retornada. Se a navegação no diretório estiver habilitada, um link para o
arquivo será exibido na lista do diretório.

O seguinte código habilita o fornecimento de tipos desconhecidos e renderiza o arquivo


desconhecido como uma imagem:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

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

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();

Com o código anterior, uma solicitação para um arquivo com um tipo de conteúdo
desconhecido é retornada como uma imagem.

2 Aviso

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

Fornecer arquivos de vários locais


Considere a página a seguir Razor que exibe o /MyStaticFiles/image3.png arquivo:

CSHTML

@page

<p> Test /MyStaticFiles/image3.png</p>

<img src="~/image3.png" class="img" asp-append-version="true" alt="Test">

UseStaticFiles e UseFileServer o padrão para o provedor de arquivos apontando

wwwroot para . Instâncias adicionais de UseStaticFiles e UseFileServer podem ser


fornecidas com outros provedores de arquivos para fornecer arquivos de outros locais.
O exemplo a seguir chama UseStaticFiles duas vezes para atender arquivos de
wwwroot e MyStaticFiles :

C#

app.UseStaticFiles(); // Serve files from wwwroot


app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"))
});

Usando o código anterior:

O /MyStaticFiles/image3.png arquivo é exibido.


Os AuxiliaresAppendVersion de Marca de Imagem não são aplicados porque os
Auxiliares de Marca dependem WebRootFileProviderde . WebRootFileProvider não
foi atualizado para incluir a MyStaticFiles pasta.

O código a seguir atualiza o WebRootFileProvider , que permite que o Auxiliar de Marca


de Imagem forneça uma versão:

C#

var webRootProvider = new


PhysicalFileProvider(builder.Environment.WebRootPath);
var newPathProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"));

var compositeProvider = new CompositeFileProvider(webRootProvider,


newPathProvider);

// Update the default provider.


app.Environment.WebRootFileProvider = compositeProvider;

app.UseStaticFiles();

Considerações de segurança para arquivos estáticos

2 Aviso

UseDirectoryBrowser e UseStaticFiles podem causar a perda de segredos. A

desabilitação da navegação no diretório em produção é altamente recomendada.


Examine com atenção os diretórios que são habilitados por meio de
UseStaticFiles ou UseDirectoryBrowser . Todo o diretório e seus subdiretórios se

tornam publicamente acessíveis. Armazene arquivos adequados para servir ao


público em um diretório dedicado, como <content_root>/wwwroot . Separe esses
arquivos dos modos de exibição MVC, Razor Páginas, arquivos de configuração etc.

As URLs para o conteúdo exposto com UseDirectoryBrowser e UseStaticFiles


estão sujeitas à diferenciação de maiúsculas e minúsculas e a restrições de
caracteres do sistema de arquivos subjacente. Por exemplo, o Windows não
diferencia maiúsculas de minúsculas, mas macOS e Linux não.

Os aplicativos ASP.NET Core hospedados no IIS usam o Módulo do ASP.NET Core


para encaminhar todas as solicitações ao aplicativo, inclusive as solicitações de
arquivo estático. O manipulador de arquivos estáticos do IIS não é usado e não
tem nenhuma chance de lidar com solicitações.

Conclua as etapas seguinte no Gerenciador do IIS para remover o manipulador de


arquivos estáticos no IIS no nível do servidor ou do site:

1. Navegue para o recurso Módulos.


2. Selecione StaticFileModule na lista.
3. Clique em Remover na barra lateral Ações.

2 Aviso

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 da raiz da Web do


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

Fornecer arquivos fora de wwwroot atualizando


IWebHostEnvironment.WebRootPath
Quando IWebHostEnvironment.WebRootPath é definido como uma pasta diferente de
wwwroot :

No ambiente de desenvolvimento, os ativos estáticos encontrados em wwwroot e


os atualizados IWebHostEnvironment.WebRootPath são fornecidos do wwwroot .
Em qualquer ambiente que não seja o desenvolvimento, ativos estáticos
duplicados são atendidos da pasta atualizada IWebHostEnvironment.WebRootPath .

Considere um aplicativo Web criado com o modelo da Web vazio:

Contendo um Index.html arquivo em wwwroot e wwwroot-custom .

Com o seguinte arquivo atualizado Program.cs que define WebRootPath =


"wwwroot-custom" :

C#
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in "wwwroot-custom"
WebRootPath = "wwwroot-custom"
});

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

app.Run();

No código anterior, solicita a / :

No retorno do ambiente de desenvolvimento wwwroot/Index.html


Em qualquer ambiente que não seja o retorno do desenvolvimento wwwroot-
custom/Index.html

Para garantir que os ativos de wwwroot-custom sejam retornados, use uma das seguintes
abordagens:

Exclua ativos nomeados duplicados no wwwroot .

Defina "ASPNETCORE_ENVIRONMENT" em como Properties/launchSettings.json


qualquer valor diferente de "Development" .

Desabilite completamente os ativos da Web estáticos definindo


<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled> no arquivo de projeto.
AVISO, desabilitar ativos da Web estáticos desabilita Razor bibliotecas de classes.

Adicione o seguinte JSON ao arquivo de projeto:

XML

<ItemGroup>
<Content Remove="wwwroot\**" />
</ItemGroup>

O código a seguir IWebHostEnvironment.WebRootPath é atualizado para um valor não de


desenvolvimento, garantindo que o conteúdo duplicado seja retornado de em vez
wwwroot de wwwroot-custom :

C#
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Examine Hosting environment: logging value
EnvironmentName = Environments.Staging,
WebRootPath = "wwwroot-custom"
});

var app = builder.Build();

app.Logger.LogInformation("ASPNETCORE_ENVIRONMENT: {env}",
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));

app.Logger.LogInformation("app.Environment.IsDevelopment(): {env}",
app.Environment.IsDevelopment().ToString());

app.UseDefaultFiles();
app.UseStaticFiles();

app.Run();

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Middleware
Introdução ao ASP.NET Core
Escolher uma interface do usuário da
Web ASP.NET Core
Artigo • 04/01/2023 • 9 minutos para o fim da leitura

ASP.NET Core é uma estrutura de interface do usuário completa. Escolha quais


funcionalidades combinar que se ajustam às necessidades da interface do usuário da
Web do aplicativo.

Benefícios versus custos da interface do


usuário renderizada por servidor e cliente
Há três abordagens gerais para criar uma interface do usuário da Web moderna com
ASP.NET Core:

Aplicativos que renderizam a interface do usuário do servidor.


Aplicativos que renderizam a interface do usuário no cliente no navegador.
Aplicativos híbridos que aproveitam as abordagens de renderização da interface
do usuário do servidor e do cliente. Por exemplo, a maior parte da interface do
usuário da Web é renderizada no servidor e os componentes renderizados do
cliente são adicionados conforme necessário.

Há benefícios e desvantagens a serem considerados ao renderizar a interface do usuário


no servidor ou no cliente.

Interface do usuário renderizada do servidor


Um aplicativo de interface do usuário da Web que é renderizado no servidor gera
dinamicamente o HTML e o CSS da página no servidor em resposta a uma solicitação de
navegador. A página chega ao cliente pronto para exibição.

Benefícios:

Os requisitos do cliente são mínimos porque o servidor faz o trabalho de geração


de página e lógica:
Ótimo para dispositivos de baixa extremidade e conexões de baixa largura de
banda.
Permite uma ampla variedade de versões do navegador no cliente.
Tempos de carregamento rápidos da página inicial.
Mínimo para nenhum JavaScript para efetuar pull para o cliente.
Flexibilidade de acesso aos recursos do servidor protegido:
Acesso ao banco de dados.
Acesso a segredos, como valores para chamadas à API para o armazenamento
do Azure.
Vantagens da análise de site estático, como a otimização do mecanismo de
pesquisa.

Exemplos de cenários comuns de aplicativos de interface do usuário da Web


renderizados pelo servidor:

Sites dinâmicos, como aqueles que fornecem páginas, dados e formulários


personalizados.
Exiba dados somente leitura, como listas de transações.
Exibir páginas de blog estáticas.
Um sistema de gerenciamento de conteúdo voltado para o público.

Desvantagens:

O custo de computação e o uso de memória estão concentrados no servidor, em


vez de em cada cliente.
As interações do usuário exigem uma viagem de ida e volta ao servidor para gerar
atualizações da interface do usuário.

Interface do usuário renderizada pelo cliente


Um aplicativo renderizado pelo cliente renderiza dinamicamente a interface do usuário
da Web no cliente, atualizando diretamente o DOM do navegador conforme necessário.

Benefícios:

Permite uma interatividade avançada que é quase instantânea, sem a necessidade


de uma viagem de ida e volta para o servidor. A manipulação de eventos e a lógica
da interface do usuário são executadas localmente no dispositivo do usuário com
latência mínima.
Dá suporte a atualizações incrementais, salvando formulários ou documentos
parcialmente concluídos sem que o usuário precise selecionar um botão para
enviar um formulário.
Pode ser projetado para ser executado em um modo desconectado. Atualizações
para o modelo do lado do cliente serão sincronizados novamente com o servidor
depois que uma conexão for restabelecida.
Carga e custo reduzidos do servidor, o trabalho é descarregado para o cliente.
Muitos aplicativos renderizados pelo cliente também podem ser hospedados
como sites estáticos.
Aproveita os recursos do dispositivo do usuário.

Exemplos de interface do usuário da Web renderizada pelo cliente:

Um painel interativo.
Um aplicativo com funcionalidade de arrastar e soltar
Um aplicativo social responsivo e colaborativo.

Desvantagens:

O código para a lógica deve ser baixado e executado no cliente, adicionando ao


tempo de carregamento inicial.
Os requisitos do cliente podem excluir os usuários que têm dispositivos de baixa
extremidade, versões mais antigas do navegador ou conexões de baixa largura de
banda.

Escolher um servidor renderizado ASP.NET Core


solução de interface do usuário
A seção a seguir explica o ASP.NET Core modelos renderizados do servidor de interface
do usuário da Web disponíveis e fornece links para começar. Razor ASP.NET Core Pages
e ASP.NET Core MVC são estruturas baseadas em servidor para criar aplicativos Web
com o .NET.

Razor páginas do ASP.NET Core


Razor Pages é um modelo baseado em página. As preocupações com a interface do
usuário e a lógica de negócios são mantidas separadas, mas dentro da página.
RazorPages é a maneira recomendada de criar novos aplicativos baseados em página ou
em formulário para desenvolvedores novos em ASP.NET Core. RazorO Pages fornece um
ponto de partida mais fácil do que ASP.NET Core MVC.

Razor Benefícios de páginas, além dos benefícios de renderização do servidor:

Crie e atualize rapidamente a interface do usuário. O código da página é mantido


com a página, mantendo as preocupações da interface do usuário e da lógica de
negócios separadas.
Testável e dimensiona para aplicativos grandes.
Mantenha suas páginas ASP.NET Core organizadas de maneira mais simples do
que ASP.NET MVC:
Os modelos de exibição e lógica específicos podem ser mantidos juntos em seu
próprio namespace e diretório.
Grupos de páginas relacionadas podem ser mantidos em seu próprio
namespace e diretório.

Para começar a usar seu primeiro aplicativo ASP.NET Core Razor Pages, consulte
Tutorial: Introdução ao Razor Pages no ASP.NET Core. Para obter uma visão geral
completa do ASP.NET Core Razor Pages, sua arquitetura e benefícios, consulte:
Introdução às Razor páginas no ASP.NET Core.

ASP.NET Core MVC


ASP.NET MVC renderiza a interface do usuário no servidor e usa um padrão de
arquitetura MVC (Model-View-Controller). O padrão MVC separa um aplicativo em três
grupos principais de componentes: Modelos, Exibições e Controladores. As solicitações
do usuário são roteada para um controlador. O controlador é responsável por trabalhar
com o modelo para executar ações do usuário ou recuperar resultados de consultas. O
controlador escolhe a exibição a ser exibida para o usuário e fornece a ele todos os
dados de modelo necessários. O suporte para Razor Pages é criado em ASP.NET Core
MVC.

Benefícios do MVC, além dos benefícios de renderização do servidor:

Com base em um modelo escalonável e maduro para a criação de aplicativos Web


grandes.
Separação clara de preocupações com a máxima flexibilidade.
A separação de responsabilidades do Model-View-Controller garante que o
modelo de negócios possa evoluir sem ser firmemente acoplado a detalhes de
implementação de baixo nível.

Para começar a usar ASP.NET Core MVC, consulte Introdução ao ASP.NET Core MVC.
Para obter uma visão geral da arquitetura e dos benefícios do ASP.NET Core MVC,
confira Visão geral do ASP.NET Core MVC.

Blazor Server
O Blazor é uma estrutura para criar uma interface do usuário web interativa do lado do
cliente com o .NET:

Crie interfaces do usuário interativas avançadas usando C# em vez de JavaScript .


Compartilhe a lógica de aplicativo do lado do cliente e do servidor gravada no
.NET.
Renderize a interface do usuário, como HTML e CSS para suporte amplo de
navegadores, incluindo navegadores móveis.
Integre-se a plataformas de hospedagem modernas, como o Docker.
Crie aplicativos móveis e de área de trabalho híbrida com .NET e Blazor.

Usar o .NET para desenvolvimento web do lado do cliente oferece as seguintes


vantagens:

escreva o código em C# em vez de JavaScript.


Aproveite o ecossistema .NET existente das bibliotecas .NET.
Compartilhe a lógica de aplicativo entre o servidor e o cliente.
Beneficie-se com o desempenho, confiabilidade e segurança do .NET.
Mantenha-se produtivo no Windows, Linux ou macOS com um ambiente de
desenvolvimento, como o Visual Studio ou o Visual Studio Code .
Crie um conjunto comum de linguagens, estruturas e ferramentas que são estáveis,
com recursos avançados e fáceis de usar.

Blazor Serverfornece suporte para hospedar a interface do usuário renderizada pelo


servidor em um aplicativo ASP.NET Core. As atualizações da interface do usuário do
cliente são tratadas em uma SignalR conexão. O runtime permanece no servidor e
manipula a execução do código C# do aplicativo.

Para obter mais informações, consulte modelos de hospedagem ASP.NET Core Blazor e
ASP.NET CoreBlazor. O modelo de hospedagem renderizado pelo Blazor cliente é
descrito na Blazor WebAssembly seção posteriormente neste artigo.

Escolher uma solução de ASP.NET Core


renderizada pelo cliente
A seção a seguir explica brevemente os modelos renderizados do cliente de interface do
usuário da Web ASP.NET Core disponíveis e fornece links para começar.

Blazor WebAssembly
Blazor WebAssembly é uma estrutura spa (aplicativo de página única) para criar
aplicativos Web interativos do lado do cliente com as características gerais descritas na
Blazor Server seção anterior neste artigo.

A execução do código do .NET em navegadores da Web é possibilitada por


WebAssembly (abreviado como wasm ). O WebAssembly é um formato de código de
bytes compacto, otimizado para download rápido e máxima velocidade de execução. O
WebAssembly é um padrão aberto da Web compatível com navegadores da Web sem
plug-ins. Blazor WebAssembly funciona em todos os navegadores da Web modernos,
incluindo os navegadores móveis.

Quando um Blazor WebAssembly aplicativo é criado e executado:

Os arquivos de código C# e do Razor são compilados em assemblies do .NET.


Os assemblies e o runtime do .NET são baixados no navegador.
O Blazor WebAssembly inicializa o runtime do .NET e o configura para carregar os
assemblies no aplicativo. O Blazor WebAssembly runtime usa a interoperabilidade
JavaScript para manipular a manipulação do DOM (Modelo de Objeto de
Documento) e as chamadas à API do navegador.

Para obter mais informações, consulte ASP.NET Core Blazor e modelos de hospedagem
ASP.NET CoreBlazor. O modelo de hospedagem renderizado pelo Blazor servidor é
descrito na Blazor Server seção anterior neste artigo.

ASP.NET Core SPA (Aplicativo de Página Única) com


Estruturas JavaScript, como Angular e React
Crie lógica do lado do cliente para aplicativos ASP.NET Core usando estruturas
JavaScript populares, como Angular ou React . ASP.NET Core fornece modelos de
projeto para Angular e React e também pode ser usado com outras estruturas
JavaScript.

Benefícios de ASP.NET Core SPA com Estruturas JavaScript, além dos benefícios de
renderização do cliente listados anteriormente:

O ambiente de runtime do JavaScript já é fornecido com o navegador.


Grande comunidade e ecossistema maduro.
Crie lógica do lado do cliente para aplicativos ASP.NET Core usando estruturas
popularesJS, como Angular e React.

Desvantagens:

Mais linguagens de codificação, estruturas e ferramentas necessárias.


Difícil compartilhar código para que alguma lógica possa ser duplicada.

Para começar. confira:

Usar Angular com ASP.NET Core


Usar React com ASP.NET Core
Escolha uma solução híbrida: ASP.NET Core
MVC ou Razor Pages plusBlazor
MVC, Razor Pages e Blazor fazem parte da estrutura ASP.NET Core e foram projetados
para serem usados juntos. Razoros componentes podem ser integrados aos Razor
aplicativos Pages e MVC em uma solução ou Blazor Server hospedadaBlazor
WebAssembly. Quando uma exibição ou página é renderizada, os componentes podem
ser pré-gerados ao mesmo tempo.

Benefícios para MVC ou Razor Pages mais Blazor, além dos benefícios do MVC ou Razor
do Pages:

A pré-geração executa componentes Razor no servidor e os renderiza em uma


exibição ou página, o que melhora o tempo de carga percebido do aplicativo.
Adicione interatividade a exibições ou páginas existentes com o Auxiliar de Marca
de Componente.

Para começar a usar ASP.NET Core MVC ou Razor Pages mais Blazor, confira Pré-gerar e
integrar componentes ASP.NET CoreRazor.

Próximas etapas
Para obter mais informações, consulte:

ASP.NET Core Blazor


Modelos de hospedagem do ASP.NET Core Blazor
Pré-renderizar e integrar componentes Razor do ASP.NET Core
Comparar serviços gRPC com APIs HTTP
Introdução ao Razor Pages no ASP.NET
Core
Artigo • 28/11/2022 • 55 minutos para o fim da leitura

Por Rick Anderson , Dave Brock e Kirk Larkin

O Razor Pages torna a codificação de cenários centrados em página mais fácil e


produtiva do que com o uso de controladores e exibições.

Se você estiver procurando um tutorial que utiliza a abordagem Modelo-Exibição-


Controlador, consulte a Introdução ao ASP.NET Core MVC.

Este documento proporciona uma introdução ao Razor Pages. Este não é um tutorial
passo a passo. Se você achar que algumas das seções são muito avançadas, consulte a
Introdução ao Razor Pages. Para obter uma visão geral do ASP.NET Core, consulte a
Introdução ao ASP.NET Core.

Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um projeto do Razor Pages


Visual Studio

Confira a Introdução ao Razor Pages para obter instruções detalhadas sobre como
criar um projeto do Razor Pages.

Razor Pages
O Razor Pages é habilitado em Program.cs :

C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

No código anterior:

AddRazorPages adiciona serviços para o Razor Pages ao aplicativo.


MapRazorPages adiciona pontos de extremidade para o Razor Pages ao
IEndpointRouteBuilder.

Considere uma página básica:

CSHTML

@page

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

O código anterior se assemelha muito a um arquivo de exibição do Razor usado em um


aplicativo ASP.NET Core com controladores e exibições. O que o torna diferentes é a
diretiva @page. O @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. Os nomes de arquivo do Razor Pages têm um sufixo .cshtml .

Uma página semelhante, usando uma classe PageModel , é mostrada nos dois arquivos a
seguir. O arquivo Pages/Index2.cshtml :
CSHTML

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>


<p>
@Model.Message
</p>

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

C#

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

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

public void OnGet()


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

Por convenção, o arquivo de classe PageModel tem o mesmo nome que o arquivo do
Razor Page com .cs acrescentado. Por exemplo, a Razor Page anterior é
Pages/Index2.cshtml . O arquivo que contém a classe PageModel chama-se

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 seguinte tabela mostra um caminho de Razor Page e a URL
correspondente:

Caminho e nome do arquivo URL correspondente

/Pages/Index.cshtml / ou /Index

/Pages/Contact.cshtml /Contact

/Pages/Store/Contact.cshtml /Store/Contact
Caminho e nome do arquivo URL correspondente

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

Observações:

O runtime procura por arquivos de Razor Pages na pasta Pages por padrão.
Index é a página padrão quando uma URL não inclui uma página.

Escrever um formulário básico


O Razor Pages foi projetado para facilitar a implementação de padrões comuns usados
com navegadores da Web ao criar um aplicativo. Model binding, Auxiliares de Marcação
e auxiliares HTML funcionam com as propriedades definidas em uma classe de Razor
Page. Considere uma página que implementa um formulário básico "Fale conosco" para
o modelo Contact :

Para as amostras neste documento, o DbContext é inicializado no arquivo Startup.cs .

O banco de dados na memória requer o pacote NuGet


Microsoft.EntityFrameworkCore.InMemory .

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();
app.MapRazorPages();

app.Run();

O modelo de dados:

C#

using System.ComponentModel.DataAnnotations;

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

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

O contexto do banco de dados:

C#

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
public class CustomerDbContext : DbContext
{
public CustomerDbContext (DbContextOptions<CustomerDbContext>
options)
: base(options)
{
}

public DbSet<RazorPagesContacts.Models.Customer> Customer =>


Set<RazorPagesContacts.Models.Customer>();
}
}

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

CSHTML

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Enter a customer name:</p>

<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

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

C#

public class CreateModel : PageModel


{
private readonly Data.CustomerDbContext _context;

public CreateModel(Data.CustomerDbContext context)


{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

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

public async Task<IActionResult> OnPostAsync()


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

if (Customer != null) _context.Customer.Add(Customer);


await _context.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. Essa separação permite:
Gerenciar dependências de página por meio da injeção de dependência.
Teste de unidade

A página tem um método de manipulador OnPostAsync , que é executado em solicitações


POST (quando um usuário posta o formulário). Métodos de manipulador para qualquer
verbo HTTP podem ser adicionados. Os manipuladores mais comuns são:

OnGet para inicializar o estado necessário para a página. No código anterior, o

método OnGet exibe a Razor Page CreateModel.cshtml .


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 anterior é comum para o Razor Pages.

Se você estiver familiarizado com aplicativos ASP.NET usando controladores e exibições:

O código OnPostAsync no exemplo anterior é semelhante ao código de


controlador típico.
A maioria dos primitivos do MVC, como model binding, validação e resultados de
ação, funcionam da mesma forma com Controladores e com o Razor Pages.

O método OnPostAsync anterior:

C#

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

public async Task<IActionResult> OnPostAsync()


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

if (Customer != null) _context.Customer.Add(Customer);


await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

O fluxo básico de OnPostAsync :

Verifique se há erros de validação.

Se não houver nenhum erro, salve os dados e redirecione.


Se houver erros, mostre a página novamente com as mensagens de validação. Em
muitos casos, erros de validação seriam detectados no cliente e nunca enviados ao
servidor.

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

CSHTML

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

<p>Enter a customer name:</p>

<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

A marca HTML renderizada de Pages/Customers/Create.cshtml :

HTML

<p>Enter a customer name:</p>

<form method="post">
Name:
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a maximum
length of 10."
data-val-length-max="10" data-val-required="The Name field is
required."
id="Customer_Name" maxlength="10" name="Customer.Name" value=""
/>
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden"
value="<Antiforgery token here>" />
</form>

No código anterior, postando o formulário:

Com dados válidos:

O método de manipulador OnPostAsync chama o método auxiliar


RedirectToPage. RedirectToPage retorna uma instância de RedirectToPageResult.
RedirectToPage :

É um resultado de ação.
É semelhante a RedirectToAction ou RedirectToRoute (usado em
controladores e exibições).
É personalizado para as páginas. Na amostra anterior, ele redireciona para a
página de Índice raiz ( /Index ). RedirectToPage é descrito em detalhes na
seção Geração de URLs para páginas.

Com erros de validação passados para o servidor:


O método de manipulador OnPostAsync chama o método auxiliar Page. Page
retorna uma instância de PageResult. Retornar Page é semelhante a como as
ações em controladores retornam View . PageResult é o tipo de retorno padrão
para um método de manipulador. Um método de manipulador que retorna
void renderiza a página.

No exemplo anterior, postar o formulário sem nenhum valor faz com que
ModelState.IsValid retorne false. Neste exemplo, nenhum erro de validação é
exibido no cliente. O tratamento de erros de validação será abordado
posteriormente neste documento.

C#

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

public async Task<IActionResult> OnPostAsync()


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

if (Customer != null) _context.Customer.Add(Customer);


await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Com erros de validação detectados pela validação do lado do cliente:


Os dados não são postados no servidor.
A validação do lado do cliente é explicada posteriormente neste documento.

A propriedade Customer usa o atributo [BindProperty] para aceitar o model binding:

C#

public class CreateModel : PageModel


{
private readonly Data.CustomerDbContext _context;
public CreateModel(Data.CustomerDbContext context)
{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

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

public async Task<IActionResult> OnPostAsync()


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

if (Customer != null) _context.Customer.Add(Customer);


await _context.SaveChangesAsync();

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

[BindProperty] não deve ser usado em modelos que contêm propriedades que não
devem ser alteradas pelo cliente. Para obter mais informações, confira Postagem em
excesso.

O Razor Pages, por padrão, associa propriedades somente com verbos não GET . A
associação a propriedades remove a necessidade de escrever código para converter
dados HTTP no tipo de modelo. 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.

2 Aviso

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 a solicitações GET , defina a propriedade
SupportsGet do atributo [BindProperty] como true :

C#

[BindProperty(SupportsGet = true)]

Para obter mais informações, confira ASP.NET Core Community Standup: Bind on
GET discussion (YouTube) .

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

CSHTML

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

<p>Enter a customer name:</p>

<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

No código anterior, o auxiliar de marcação de entrada <input asp-


for="Customer.Name" /> associa o elemento HTML <input> à expressão de modelo

Customer.Name .

@addTagHelper disponibiliza os Auxiliares de Marca.

Home page
Index.cshtml é a página inicial:

CSHTML

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

<h1>Contacts home page</h1>


<form method="post">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
@if (Model.Customers != null)
{
foreach (var contact in Model.Customers)
{
<tr>
<td> @contact.Id </td>
<td>@contact.Name</td>
<td>
<!-- <snippet_Edit> -->
<a asp-page="./Edit" asp-route-
id="@contact.Id">Edit</a> |
<!-- </snippet_Edit> -->
<!-- <snippet_Delete> -->
<button type="submit" asp-page-handler="delete" asp-
route-id="@contact.Id">delete</button>
<!-- </snippet_Delete> -->
</td>
</tr>
}
}
</tbody>
</table>
<a asp-page="Create">Create New</a>
</form>

A classe PageModel associada ( Index.cshtml.cs ):

C#

public class IndexModel : PageModel


{
private readonly Data.CustomerDbContext _context;
public IndexModel(Data.CustomerDbContext context)
{
_context = context;
}

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

public async Task OnGetAsync()


{
Customers = await _context.Customer.ToListAsync();
}

public async Task<IActionResult> OnPostDeleteAsync(int id)


{
var contact = await _context.Customer.FindAsync(id);
if (contact != null)
{
_context.Customer.Remove(contact);
await _context.SaveChangesAsync();
}

return RedirectToPage();
}
}

O arquivo Index.cshtml contém o seguinte markup:

CSHTML

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

O <a /a> Auxiliar de Marcação de Âncora usou o atributo asp-route-{value} para gerar
um link para a página Editar. O link contém dados de rota com a ID de contato. Por
exemplo, https://localhost:5001/Edit/1 . Os Auxiliares de Marcação permitem que o
código do servidor participe da criação e renderização de elementos HTML em arquivos
do Razor.

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


para cada contato de cliente:

CSHTML

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


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

O HTML renderizado:

HTML

<button type="submit" formaction="/Customers?


id=1&amp;handler=delete">delete</button>

Quando o botão de exclusão é renderizado no HTML, o 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 asp-page-handler for

definido como um valor diferente, como remove , um método de manipulador com o


nome OnPostRemoveAsync será selecionado.

C#

public async Task<IActionResult> OnPostDeleteAsync(int id)


{
var contact = await _context.Customer.FindAsync(id);

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

return RedirectToPage();
}

O método OnPostDeleteAsync :

Obtém 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, ele será removido e o banco de dados será
atualizado.
Chama RedirectToPage para redirecionar para a página de índice de raiz ( /Index ).

O arquivo Edit.cshtml
CSHTML

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel

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

<h1>Edit</h1>

<h4>Customer</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="Customer!.Id" />
<div class="form-group">
<label asp-for="Customer!.Name" class="control-label">
</label>
<input asp-for="Customer!.Name" class="form-control" />
<span asp-validation-for="Customer!.Name" 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-page="./Index">Back to List</a>
</div>

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

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 runtime retorna um erro HTTP 404 (não encontrado). Para
tornar a ID opcional, acrescente ? à restrição de rota:

CSHTML

@page "{id:int?}"

O arquivo Edit.cshtml.cs :

C#

public class EditModel : PageModel


{
private readonly RazorPagesContacts.Data.CustomerDbContext _context;

public EditModel(RazorPagesContacts.Data.CustomerDbContext context)


{
_context = context;
}
[BindProperty]
public Customer? Customer { get; set; }

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


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

Customer = await _context.Customer.FirstOrDefaultAsync(m => m.Id ==


id);

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

// To protect from overposting attacks, enable the specific properties


you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}

if (Customer != null)
{
_context.Attach(Customer).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerExists(Customer.Id))
{
return NotFound();
}
else
{
throw;
}
}
}

return RedirectToPage("./Index");
}
private bool CustomerExists(int id)
{
return _context.Customer.Any(e => e.Id == id);
}
}

Validação
Regras de validação:

São especificadas declarativamente na classe de modelo.


São impostas em todos os lugares do aplicativo.

O namespace System.ComponentModel.DataAnnotations fornece um conjunto de


atributos de validação internos 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.

Considere o modelo Customer :

C#

using System.ComponentModel.DataAnnotations;

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

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

Usando o seguinte arquivo de exibição Create.cshtml :

CSHTML

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

<p>Validation: customer name:</p>

<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer.Name"></span>
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>

O código anterior:

Inclui jQuery e scripts de validação de jQuery.

Usa o <div /> e <span /> Auxiliares de Marcação para habilitar:


Validação do lado do cliente.
Renderização de erro de validação.

Gera o seguinte HTML:

HTML

<p>Enter a customer name:</p>

<form method="post">
Name:
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a
maximum length of 10."
data-val-length-max="10" data-val-required="The Name field
is required."
id="Customer_Name" maxlength="10" name="Customer.Name"
value="" />
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden"
value="<Antiforgery token here>" />
</form>

<script src="/lib/jquery/dist/jquery.js"></script>
<script src="/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>

Postar o formulário Criar sem um valor de nome exibe a mensagem de erro "O campo
Nome é necessário" no formulário. Se o JavaScript estiver habilitado no cliente, o
navegador exibirá o erro sem postar no servidor.

O atributo [StringLength(10)] gera data-val-length-max="10" no HTML renderizado.


data-val-length-max impede que navegadores insiram mais do que o comprimento
máximo especificado. Se uma ferramenta como o Fiddler for usada para editar e
reproduzir a postagem:

Com o nome maior que 10.


A mensagem de erro "O nome do campo deve ser uma cadeia de caracteres com
comprimento máximo de 10" será retornada.

Considere o seguinte modelo Movie :

C#

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

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

[StringLength(60, MinimumLength = 3)]


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

[Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
[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 a ser imposto 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 anterior, "Gênero":
Deve usar apenas letras.
A primeira letra deve ser maiúscula. Espaços em branco, números e caracteres
especiais não são permitidos.

A "Classificação" RegularExpression :
Exige que o primeiro caractere seja uma letra maiúscula.
Permite caracteres especiais e números nos espaços subsequentes. "PG-13" é
válido para uma classificação, mas é um erro em "Gênero".

O atributo Range restringe um valor a um intervalo especificado.

O atributo StringLength define 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] .

A página Criar para o modelo Movie mostra erros com valores inválidos:
Para obter mais informações, consulte:

Adicionar validação ao aplicativo Movie


Validação de modelo no ASP.NET Core.
Isolamento de CSS
Isole estilos CSS em páginas, exibições e componentes individuais para reduzir ou evitar:

Dependências de estilos globais que podem ser desafiadoras de manter.


Conflitos de estilo em conteúdo aninhado.

Para adicionar um arquivo CSS com escopo para uma página ou exibição, coloque os
estilos CSS em um arquivo .cshtml.css complementar correspondente ao nome do
arquivo .cshtml . No exemplo a seguir, um arquivo Index.cshtml.css fornece estilos
CSS aplicados somente à página ou exibição Index.cshtml .

Pages/Index.cshtml.css (Razor Pages) ou Views/Index.cshtml.css (MVC):

css

h1 {
color: red;
}

O isolamento de CSS ocorre no momento do build. A estrutura reescreve seletores de


CSS para fazer a correspondência da marcação renderizada pelas páginas ou exibições
do aplicativo. Os estilos CSS reescritos são empacotados e produzidos como um ativo
estático, {APP ASSEMBLY}.styles.css . O espaço reservado {APP ASSEMBLY} é o nome do
assembly do projeto. Um link para os estilos CSS empacotados é colocado no layout do
aplicativo.

No conteúdo <head> do aplicativo Pages/Shared/_Layout.cshtml (Razor Pages) ou


Views/Shared/_Layout.cshtml (MVC), adicione ou confirme a presença do link para os

estilos CSS empacotados:

HTML

<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />

No seguinte exemplo, o nome do assembly do aplicativo é WebApp :

HTML

<link rel="stylesheet" href="WebApp.styles.css" />

Os estilos definidos em um arquivo CSS com escopo são aplicados apenas à saída
renderizada do arquivo correspondente. No exemplo anterior, as declarações CSS h1
definidas em outro lugar no aplicativo não entram em conflito com o estilo de título de
Index . O estilo CSS em cascata e as regras de herança permanecem em vigor para
arquivos CSS com escopo. Por exemplo, estilos aplicados diretamente a um elemento
<h1> no arquivo Index.cshtml substituem os estilos do arquivo CSS com escopo em
Index.cshtml.css .

7 Observação

Para garantir o isolamento do estilo CSS quando o agrupamento ocorrer, não há


suporte para a importação de CSS em blocos de código de Razor.

O isolamento de CSS se aplica apenas a elementos HTML. Não há suporte para


isolamento de CSS para Auxiliares de Marcação.

Dentro do arquivo CSS empacotado, cada página, exibição ou componente Razor é


associado a um identificador de escopo no formato b-{STRING} , em que o espaço
reservado {STRING} é uma cadeia de caracteres de dez caracteres gerada pela estrutura.
O seguinte exemplo fornece o estilo do elemento <h1> anterior na página Index de um
aplicativo Razor Pages:

css

/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
color: red;
}

Na página Index em que o estilo CSS é aplicada do arquivo empacotado, o


identificador de escopo é acrescentado como um atributo HTML:

HTML

<h1 b-3xxtam6d07>

O identificador é exclusivo para um aplicativo. No momento da compilação, um pacote


de projeto é criado com a convenção {STATIC WEB ASSETS BASE
PATH}/Project.lib.scp.css , em que o espaço reservado {STATIC WEB ASSETS BASE PATH}

é o caminho base dos ativos da Web estáticos.

Se outros projetos forem utilizados, como pacotes NuGet ou bibliotecas de classes


Razor, o arquivo empacotado:
Fará referência aos estilos que usam importações de CSS.
Não será publicado como um ativo da Web estático do aplicativo que consome os
estilos.

Suporte para pré-processador de CSS


Pré-processadores de CSS são úteis para aprimorar o desenvolvimento de CSS
utilizando recursos como variáveis, aninhamento, módulos, mixins e herança. Embora o
isolamento de CSS não dê suporte nativo a pré-processadores CSS, como Sass ou Less,
a integração de pré-processadores de CSS é contínua desde que a compilação do pré-
processador ocorra antes que a estrutura reescreva os seletores de CSS durante o
processo de build. Usando o Visual Studio, por exemplo, configure a compilação de pré-
processador existente como uma tarefa Before Build no Gerenciador do Executor de
Tarefas do Visual Studio.

Muitos pacotes NuGet de terceiros, como Delegate.SassBuilder , podem compilar


arquivos SASS/SCSS no início do processo de build antes que o isolamento de CSS
ocorra e nenhuma configuração adicional seja necessária.

Configuração do isolamento de CSS


O isolamento de CSS permite a configuração de alguns cenários avançados, como
quando há dependências em ferramentas ou fluxos de trabalho existentes.

Personalizar o formato de identificador de escopo


Nesta seção, o espaço reservado {Pages|Views} é Pages para aplicativos Razor Pages ou
Views para aplicativos MVC.

Por padrão, os identificadores de escopo usam o formato b-{STRING} , em que o espaço


reservado {STRING} é uma cadeia de caracteres de dez caracteres gerada pela estrutura.
Para personalizar o formato do identificador de escopo, atualize o arquivo de projeto
para um padrão desejado:

XML

<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>
No exemplo anterior, o CSS gerado para Index.cshtml.css altera o identificador de
escopo de b-{STRING} para custom-scope-identifier .

Use identificadores de escopo para obter a herança com arquivos CSS com escopo. No
exemplo de arquivo de projeto a seguir, um arquivo BaseView.cshtml.css contém
estilos comuns entre exibições. Um arquivo DerivedView.cshtml.css herda esses estilos.

XML

<ItemGroup>
<None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-
identifier" />
<None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-
scope-identifier" />
</ItemGroup>

Use o operador curinga ( * ) para compartilhar identificadores de escopo em vários


arquivos:

XML

<ItemGroup>
<None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

Alterar o caminho base para ativos da Web estáticos


O arquivo CSS com escopo é gerado na raiz do aplicativo. No arquivo de projeto, use a
propriedade StaticWebAssetBasePath para alterar o caminho padrão. O seguinte
exemplo coloca o arquivo CSS com escopo, e o restante dos ativos do aplicativo, no
caminho _content :

XML

<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

Desabilitar o agrupamento automático


Para recusar como a estrutura publica e carrega os arquivos com escopo no runtime,
use a propriedade DisableScopedCssBundling . Ao usar essa propriedade, outras
ferramentas ou processos são responsáveis por tirar os arquivos CSS isolados do
diretório obj e publicá-los e carregá-los em runtime:

XML

<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Suporte à RCL (biblioteca de classes) Razor


Quando uma RCL (biblioteca de classes) Razor fornece estilos isolados, o atributo
<link> da marca href aponta para {STATIC WEB ASSET BASE PATH}/{PACKAGE

ID}.bundle.scp.css , onde estão os espaços reservados:

{STATIC WEB ASSET BASE PATH} : o caminho de base do ativo da Web estático.

{PACKAGE ID} : o identificador de pacote da biblioteca. O identificador de pacote

usará como padrão o nome do assembly do projeto se não for especificado no


arquivo de projeto.

No exemplo a seguir:

O caminho de base do ativo da Web estático é _content/ClassLib .


O nome do assembly da biblioteca de classes é ClassLib .

Pages/Shared/_Layout.cshtml (Razor Pages) ou Views/Shared/_Layout.cshtml (MVC):

HTML

<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">

Para obter mais informações sobre RCLs, consulte os seguintes artigos:

Interface do usuário do Razor reutilizável em bibliotecas de classes com o ASP.NET


Core
Consumir componentes do Razor de uma Razor biblioteca de classes (RCL)

Para obter informações sobre o isolamento de CSS de Blazor, consulte Isolamento de


CSS do Blazor do ASP.NET Core.

Manipular solicitações HEAD com um fallback


de manipulador OnGet
As solicitações HEAD permitem recuperar os cabeçalhos de um recurso específico.
Diferente das solicitações GET , as solicitações HEAD não retornam um corpo de resposta.

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

C#

public void OnHead()


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

O Razor Pages chama o manipulador OnGet quando nenhum manipulador OnHead é


definido.

XSRF/CSRF e Razor Pages


Os Razor Pages são protegidos pela Validação antifalsificação. O FormTagHelper injeta
tokens antifalsificação em elementos de formulário HTML.

Usando Layouts, parciais, modelos e Auxiliares


de Marcação com o Razor Pages
As páginas funcionam com todos os recursos do mecanismo de exibição do Razor.
Layouts, parciais, modelos, Auxiliares de Marcação, _ViewStart.cshtml e
_ViewImports.cshtml funcionam da mesma forma que exibições convencionais do
Razor.

Organizaremos essa página aproveitando alguns desses recursos.

Adicione uma página de layout para Pages/Shared/_Layout.cshtml :

CSHTML

<!DOCTYPE html>
<html>
<head>
<title>RP Sample</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<a asp-page="/Index">Home</a>
<a asp-page="/Customers/Create">Create</a>
<a asp-page="/Customers/Index">Customers</a> <br />
@RenderBody()
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>
</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.
O conteúdo da página Razor é renderizado onde @RenderBody() é chamado.

Veja página de layout para obter mais informações.

A propriedade Layout é definida em Pages/_ViewStart.cshtml :

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.

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 :

CSHTML

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@namespace é explicado posteriormente no tutorial. A diretiva @addTagHelper coloca os

auxiliares de marcas internos em todas as páginas na pasta Pages.

A diretiva @namespace definida em uma página:

CSHTML

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

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

A diretiva @namespace 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:

C#

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

public EditModel(AppDbContext db)


{
_db = db;
}

// Code removed for brevity.

O arquivo Pages/_ViewImports.cshtml define o seguinte namespace:

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

O namespace gerado para o Pages/Customers/Edit.cshtml Razor Page é o mesmo que a


classe PageModel .

@namespace também funciona com exibições convencionais do Razor.

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

CSHTML

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

<p>Validation: customer name:</p>

<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer.Name"></span>
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>

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


_ViewImports.cshtml e o arquivo de layout anterior:

CSHTML

@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
No código anterior, o _ViewImports.cshtml importou o namespace e os Auxiliares de
Marcação. O arquivo de layout importou os arquivos JavaScript.

O projeto inicial do Razor Pages contém o Pages/_ValidationScriptsPartial.cshtml ,


que conecta a validação do lado do cliente.

Para obter mais informações sobre exibições parciais, consulte Exibições parciais no
ASP.NET Core.

Geração de URL para Páginas


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

C#

public class CreateModel : PageModel


{
private readonly CustomerDbContext _context;

public CreateModel(CustomerDbContext context)


{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

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

public async Task<IActionResult> OnPostAsync()


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

_context.Customers.Add(Customer);
await _context.SaveChangesAsync();

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

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

/Pages
Index.cshtml

Privacy.cshtml

/Clientes
Create.cshtml
Edit.cshtml

Index.cshtml

As páginas Pages/Customers/Create.cshtml e Pages/Customers/Edit.cshtml


redirecionam para Pages/Customers/Index.cshtml após o êxito. A cadeia de caracteres
./Index é um nome de página relativo usado para acessar a página anterior. Ele é
usado para gerar URLs para a página Pages/Customers/Index.cshtml . Por exemplo:

Url.Page("./Index", ...)

<a asp-page="./Index">Customers Index Page</a>


RedirectToPage("./Index")

O nome da página absoluto /Index é usado para gerar URLs para a página
Pages/Index.cshtml . Por exemplo:

Url.Page("/Index", ...)

<a asp-page="/Index">Home 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 usando diferentes parâmetros de RedirectToPage
em Pages/Customers/Create.cshtml .

RedirectToPage(x) ?

RedirectToPage("/Index") Pages/Index

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

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

RedirectToPage("Index") Pages/Customers/Index
RedirectToPage("Index") , RedirectToPage("./Index") e RedirectToPage("../Index") são

nomes relativos. O parâmetro RedirectToPage é combinado com o caminho da página


atual para calcular o nome da página de destino.

Vinculação de nome relativo é útil ao criar sites com uma estrutura complexa. Quando
nomes relativos são usados para vincular páginas em uma pasta:

Renomear uma pasta não interrompe os links relativos.


Os links não são interrompidos porque não incluem o nome da pasta.

Para redirecionar para uma página em uma área diferente, especifique essa área:

C#

RedirectToPage("/Index", new { area = "Services" });

Para obter mais informações, consulte Áreas no ASP.NET Core e Convenções de rota e
aplicativo do Razor no ASP.NET Core.

Atributo ViewData
Dados podem ser passados para uma página com ViewDataAttribute. Propriedades com
o atributo [ViewData] têm seus valores armazenados e carregados do
ViewDataDictionary.

No seguintes exemplo, o AboutModel aplica o atributo [ViewData] à propriedade Title :

C#

public class AboutModel : PageModel


{
[ViewData]
public string Title { get; } = "About";

public void OnGet()


{
}
}

Na página Sobre, acesse a propriedade Title como uma propriedade de modelo:

CSHTML

<h1>@Model.Title</h1>
No layout, o título é lido a partir do dicionário ViewData:

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...

TempData
O ASP.NET Core expõe o TempData. 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.

Os conjuntos de código a seguir definem o valor de Message usando TempData :

C#

public class CreateDotModel : PageModel


{
private readonly AppDbContext _db;

public CreateDotModel(AppDbContext db)


{
_db = db;
}

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

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

public async Task<IActionResult> OnPostAsync()


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

_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
A marcação a seguir no arquivo Pages/Customers/Index.cshtml exibe o valor de Message
usando TempData .

CSHTML

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

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


propriedade Message .

C#

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

Para obter mais informações, confira TempData.

Vários manipuladores por página


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

CSHTML

@page
@model CreateFATHModel

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

C#

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

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

public CreateFATHModel(AppDbContext db)


{
_db = db;
}

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

public async Task<IActionResult> OnPostJoinListAsync()


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

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

public async Task<IActionResult> OnPostJoinListUCAsync()


{
if (!ModelState.IsValid)
{
return Page();
}
Customer.Name = Customer.Name?.ToUpperInvariant();
return await OnPostJoinListAsync();
}
}
}
O código anterior usa métodos de manipulador nomeados. Métodos de manipulador
nomeados são criados colocando o texto no nome após On<HTTP Verb> e antes de
Async (se houver). No exemplo anterior, os métodos de página são

OnPostJoinListAsync e OnPostJoinListUCAsync. Com OnPost e Async removidos, os


nomes de manipulador são JoinList e JoinListUC .

CSHTML

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


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

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


https://localhost:5001/Customers/CreateFATH?handler=JoinList . O caminho da URL
que envia a OnPostJoinListUCAsync é https://localhost:5001/Customers/CreateFATH?
handler=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" .

Se você não deseja a cadeia de consulta ?handler=JoinList na URL, altere a rota para
colocar o nome do manipulador na parte do caminho da URL. A rota pode ser
personalizada adicionando um modelo de rota entre aspas duplas após a diretiva @page .

CSHTML

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

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


https://localhost:5001/Customers/CreateFATH/JoinList . O caminho da URL que envia a

OnPostJoinListUCAsync é https://localhost:5001/Customers/CreateFATH/JoinListUC .

O ? após handler significa que o parâmetro de rota é opcional.

Configuração e definições avançadas


A configuração e as definições nas seções a seguir não são exigidas pela maioria dos
aplicativos.

Para configurar opções avançadas, use a sobrecarga AddRazorPages que configura


RazorPagesOptions:

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
options.RootDirectory = "/MyPages";
options.Conventions.AuthorizeFolder("/MyPages/Admin");
});

builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Use o RazorPagesOptions para definir o diretório raiz para páginas ou adicionar as


convenções de modelo de aplicativo para páginas. Para obter mais informações sobre
convenções, consulte Convenções de autorização do Razor Pages.

Para pré-compilar exibições, consulte Compilação de exibição do Razor.

Especificar que Razor Pages estão na raiz do conteúdo


Por padrão, Razor Pages estão na raiz do diretório /Pages. Adicione
WithRazorPagesAtContentRoot para especificar que Razor Pages estão na raiz de
conteúdo (ContentRootPath) do aplicativo:

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesAtContentRoot();

builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Especificar que Razor Pages estão em um diretório raiz


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

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesRoot("/path/to/razor/pages");

builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
Recursos adicionais
Consulte Introdução ao Razor Pages, que se baseia nesta introdução.
Atributo de autorização do Razor Pages
Baixar ou exibir código de exemplo
Visão geral do ASP.NET Core
Razor Referência de sintaxe para ASP.NET Core
Áreas no ASP.NET Core
Tutorial: introdução ao Razor Pages no ASP.NET Core
Convenções de autorização do Razor Pages no ASP.NET Core
Convenções de rota e aplicativo do Razor Pages no ASP.NET Core
Testes de unidade do Razor Pages no ASP.NET Core
Exibições parciais no ASP.NET Core
Pré-renderizar e integrar componentes Razor do ASP.NET Core
Tutorial: criar um aplicativo Web Razor
Pages com o ASP.NET Core
Artigo • 02/12/2022 • 2 minutos para o fim da leitura

Esta série de tutoriais explica as noções básicas sobre a criação de um aplicativo Web
Razor Pages.

Para ver uma introdução mais avançada voltada para desenvolvedores familiarizados
com controladores e exibições, confira Introdução ao Razor Pages no ASP.NET Core.

Se não estiver familiarizado com o desenvolvimento de ASP.NET Core e não tiver


certeza de qual solução de interface do usuário da Web do ASP.NET Core atenderá
melhor às suas necessidades, consulte Escolher uma interface do usuário do ASP.NET
Core.

Esta série inclui os seguintes tutoriais:

1. Criar um aplicativo Web Razor Pages


2. Adicionar um modelo a um aplicativo Razor Pages
3. Scaffold (gerar) páginas Razor
4. Trabalhar com um banco de dados
5. Atualizar páginas 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 ao Razor Pages no
ASP.NET Core
Artigo • 02/12/2022 • 24 minutos para o fim da leitura

De Rick Anderson

Este é o primeiro tutorial de uma série que ensina os conceitos básicos da criação de um
aplicativo Web ASP.NET Core Razor Pages.

Para obter uma introdução mais avançada voltada para desenvolvedores que estão
familiarizados com controladores e exibições, consulte Introdução às Razor páginas.
Para obter uma introdução em vídeo, consulte Entity Framework Core para iniciantes .

Se não estiver familiarizado com o desenvolvimento de ASP.NET Core e não tiver


certeza de qual solução de interface do usuário da Web do ASP.NET Core atenderá
melhor às suas necessidades, consulte Escolher uma interface do usuário do ASP.NET
Core.

No final deste tutorial, você terá um Razor aplicativo Web Pages que gerencia um banco
de dados de filmes.

Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.
Criar um aplicativo Web Razor Pages
Visual Studio

Inicie o Visual Studio e selecione Criar um projeto.

Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core


Aplicativo> WebAvançar.

Na caixa de diálogo Configurar seu novo projeto , insira RazorPagesMovie


para Nome do projeto. É importante nomear o projeto RazorPagesMovie,
incluindo a correspondência da capitalização, para que os namespaces
correspondam quando você copiar e colar o código de exemplo.

Selecione Avançar.

Na caixa de diálogo Informações adicionais:


Selecione .NET 7.0.
Verificar: não usar instruções de nível superior está desmarcada .

Selecione Criar.
O seguinte projeto inicial é criado:

Para obter abordagens alternativas para criar o projeto, consulte Criar um novo
projeto no Visual Studio.
Execute o aplicativo
Visual Studio

Selecione RazorPagesMovie no Gerenciador de Soluções e pressione Ctrl+F5 para


executar sem o depurador.

O Visual Studio exibe a seguinte caixa de diálogo quando um projeto ainda não
está configurado para usar o SSL:

Selecione Sim se você confia no certificado SSL do IIS Express.

A seguinte caixa de diálogo é exibida:

Selecione Sim se você concordar com confiar no certificado de desenvolvimento.


Para obter informações sobre como confiar no navegador Firefox, consulte Firefox
SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

Visual Studio:

Executa o aplicativo, que inicia o Kestrel servidor.


Inicia o navegador padrão em https://localhost:<port> , que exibe a
interface do usuário dos aplicativos. <port> é a porta aleatória atribuída
quando o aplicativo foi criado.

Examinar os arquivos de projeto


As seções a seguir contêm uma visão geral das pastas e arquivos principais do projeto
com os quais você trabalhará em tutoriais posteriores.

Pasta Páginas
Contém Razor páginas e arquivos de suporte. Cada Razor página é um par de arquivos:

Um .cshtml arquivo que tem marcação HTML com código C# usando Razor
sintaxe.
Um .cshtml.cs arquivo que tem código C# que manipula eventos de página.

Arquivos de suporte têm nomes que começam com um sublinhado. Por exemplo, o
_Layout.cshtml arquivo configura elementos de interface do usuário comuns a todas as
páginas. _Layout.cshtml configura o menu de navegação na parte superior da página e
o aviso de direitos autorais na parte inferior da página. Saiba mais em Layout no
ASP.NET Core.

Pasta wwwroot
Contém ativos estáticos, como arquivos HTML, arquivos JavaScript e arquivos CSS. Saiba
mais em Arquivos estáticos no ASP.NET Core.

appsettings.json

Contém dados de configuração, como cadeias de conexão. Para obter mais informações,
consulte Configuração no ASP.NET Core.
Module.vb
Contém o seguinte código:

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

As seguintes linhas de código neste arquivo criam um WebApplicationBuilder com


padrões pré-configurados, adicionam Razor suporte ao Pages ao contêiner di (injeção
de dependência) e criam o aplicativo:

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

A página de exceção do desenvolvedor é habilitada por padrão e fornece informações


úteis sobre exceções. Os aplicativos de produção não devem ser executados no modo
de desenvolvimento porque a página de exceção do desenvolvedor pode vazar
informações confidenciais.

O código a seguir define o ponto de extremidade de exceção como /Error e habilita o


protocolo HSTS quando o aplicativo não está em execução no modo de
desenvolvimento:

C#

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

Por exemplo, o código anterior é executado quando o aplicativo está no modo de


produção ou teste. Para obter mais informações, confira Usar vários ambientes no
ASP.NET Core.

O código a seguir habilita vários middlewares:

app.UseHttpsRedirection(); : redireciona solicitações HTTP para HTTPS.

app.UseStaticFiles(); : permite que arquivos estáticos, como HTML, CSS,


imagens e JavaScript sejam atendidos. Saiba mais em Arquivos estáticos no
ASP.NET Core.
app.UseRouting(); : adiciona a correspondência de rotas ao pipeline de
middleware. Para obter mais informações, consulte Roteamento no ASP.NET Core
app.MapRazorPages(); : configura o roteamento de ponto de extremidade para
Razor o Pages.
app.UseAuthorization(); : autoriza um usuário a acessar recursos seguros. Esse

aplicativo não usa autorização, portanto, essa linha pode ser removida.
app.Run(); : executa o aplicativo.

Solução de problemas com o exemplo


concluído
Se você encontrar um problema que não possa resolver, compare seu código com o
projeto concluído. Exibir ou baixar o projeto concluído (como baixar).
Próximas etapas
Próximo: Adicionar um modelo
Parte 2, adicione um modelo a um
Razor aplicativo Pages no ASP.NET Core
Artigo • 08/12/2022 • 48 minutos para o fim da leitura

Neste tutorial, classes são adicionadas para gerenciar filmes em um banco de dados. As
classes de modelo do aplicativo usam o Entity Framework Core (EF Core) para trabalhar
com o banco de dados. EF Core é um mapeador relacional de objeto (O/RM) que
simplifica o acesso a dados. Você escreve as classes de modelo primeiro e EF Core cria o
banco de dados.

As classes de modelo são conhecidas como classes POCO (de "Plain-O ld CLR Objects")
porque não têm uma dependência de EF Core. Elas definem as propriedades dos dados
que são armazenados no banco de dados.

Adicionar um modelo de dados


Visual Studio

1. Em Gerenciador de Soluções, clique com o botão direito do mouse no


Razorprojeto> PagesMovie Adicionar>Nova Pasta. Nomeie a pasta Models .

2. Clique com o botão direito do mouse na Models pasta. Selecione


Adicionar>Classe. Dê à classe o nome Movie.

3. Adicione as seguintes propriedades à classe Movie :

C#

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; }
}
A classe Movie contém:

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

Um atributo [DataType] que especifica o tipo de dados na ReleaseDate


propriedade . Com esse atributo:
O usuário não precisa inserir informações de hora no campo de data.
Somente a data é exibida, não as informações de tempo.

O ponto de interrogação após string indica que a propriedade é anulável.


Para obter mais informações, confira Tipos de referência anuláveis.

DataAnnotations são abordados em um tutorial posterior.

Crie o projeto para verificar se não há erros de compilação.

Fazer scaffold do modelo de filme


Nesta seção, é feito o scaffold do modelo de filme. Ou seja, a ferramenta de scaffolding
gera páginas para operações de CRUD (Criar, Ler, Atualizar e Excluir) para o modelo do
filme.

Visual Studio

1. Crie a pasta Páginas/Filmes :


a. Clique com o botão direito do mouse na pasta >PáginasAdicionar>Nova
Pasta.
b. Nomeie a pasta Filmes.

2. Clique com o botão direito do mouse na pasta


>Páginas/FilmesAdicionar>Novo Item Com Scaffolded.
3. Na caixa de diálogo Adicionar Novo Scaffold , selecione Razor Páginas
usando o CRUD (Entity Framework)>Adicionar.
4. Conclua a caixa de diálogo Adicionar Razor Páginas usando o CRUD (Entity
Framework ):
a. Na lista suspensa Classe de modelo, selecione Filme
(RazorPagesMovie.Models).
b. Na linha Classe de contexto de dados, selecione o sinal de + (adição).
i. Na caixa de diálogo Adicionar Contexto de Dados , o nome
RazorPagesMovie.Data.RazorPagesMovieContext da classe é gerado.
c. Selecione Adicionar.

O appsettings.json arquivo é atualizado com a cadeia de conexão usada para se


conectar a um banco de dados local.

Arquivos criados e atualizados


O processo de scaffold cria os arquivos a seguir:

Pages/Movies: Create, Delete, Details, Edit e Index.


Data/RazorPagesMovieContext.cs

Os arquivos criados são explicados no próximo tutorial.

O processo de scaffold adiciona o seguinte código realçado ao Program.cs arquivo:

Visual Studio

C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

As Program.cs alterações são explicadas posteriormente neste tutorial.

Criar o esquema de banco de dados inicial


usando o recurso de migração do EF
O recurso de migrações no Entity Framework Core fornece uma maneira de:

Crie o esquema inicial do banco de dados.


Atualize incrementalmente o esquema de banco de dados para mantê-lo em
sincronia com o modelo de dados do aplicativo. Os dados existentes no banco de
dados são preservados.
Visual Studio

Nesta seção, a janela PMC ( Console do Gerenciador de Pacotes ) é usada para:

Adicionar uma migração inicial.


Atualize o banco de dados com a migração inicial.

1. No menu Ferramentas selecione Gerenciador de Pacotes NuGet>Console do


Gerenciador de Pacotes.

2. No PMC, insira os seguintes comandos:

PowerShell

Add-Migration InitialCreate
Update-Database

Os comandos anteriores:

Instale as ferramentas mais recentes do Entity Framework Core depois de


desinstalar qualquer versão anterior, se ela existir.
Execute o migrations comando para gerar o código que cria o esquema inicial do
banco de dados.

O seguinte aviso é exibido, que é abordado em uma etapa posterior:


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

O comando migrations gera código para criar o esquema de banco de dados inicial. O
esquema é baseado no modelo especificado em DbContext . 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 update comando executa o Up método em migrações que não foram aplicadas.


Nesse caso, update executa o Up método no Migrations/<time-
stamp>_InitialCreate.cs arquivo , que cria o banco de dados.

Examinar o contexto registrado com a injeção de


dependência
O ASP.NET Core é construído com a injeção de dependência. Serviços, como o contexto
do EF Core banco de dados, são registrados com injeção de dependência durante a
inicialização do aplicativo. Componentes que exigem esses serviços (como Razor Pages)
são fornecidos por meio de parâmetros de construtor. O código de construtor que
obtém uma instância de contexto do banco de dados é mostrado mais adiante no
tutorial.

A ferramenta de scaffolding criou automaticamente um contexto de banco de dados e o


registrou com o contêiner de injeção de dependência. O código realçado a seguir é
adicionado ao Program.cs arquivo pelo scaffolder:

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O contexto RazorPagesMovieContext de dados:

Deriva de Microsoft. EntityFrameworkCore.DbContext.


Especifica quais entidades estão incluídas no modelo de dados.
EF Core Coordena a funcionalidade, como Criar, Ler, Atualizar e Excluir, para o
Movie modelo.

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Data
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext
(DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}
public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; } =
default!;
}
}

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 desenvolvimento local, o sistema de
configuração lê a cadeia de conexão do appsettings.json arquivo.

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

Se você receber o seguinte erro:

Console

SqlException: Cannot open database "RazorPagesMovieContext-GUID"


requested by the login. The login failed.
Login failed for user 'User-name'.

Você perdeu a etapa de migrações.

2. Teste o link Criar Novo .


7 Observação

Talvez você não consiga inserir casas decimais ou vírgulas no campo Price .
Para dar suporte à validação do jQuery para localidades 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 .

3. Teste os links Editar, Detalhes e Excluir.

O tutorial a seguir explica os arquivos criados por scaffolding.

Solução de problemas com o exemplo


concluído
Se você encontrar um problema que não possa resolver, compare seu código com o
projeto concluído. Exibir ou baixar o projeto concluído (como baixar).

Recursos adicionais
Anterior: Introdução ao Próximo: Páginas com Scaffolded Razor
Parte 3, páginas scaffolded Razor no
ASP.NET Core
Artigo • 02/12/2022 • 26 minutos para o fim da leitura

De Rick Anderson

Este tutorial examina as Razor Páginas criadas por scaffolding no tutorial anterior.

As páginas Create, Delete, Details e Edit


Examine o Pages/Movies/Index.cshtml.cs modelo de página:

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies;

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

public IList<Movie> Movie { get;set; } = default!;

public async Task OnGetAsync()


{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}

Razor As páginas são derivadas de PageModel. Por convenção, a PageModel classe


derivada é chamada PageNameModel de . Por exemplo, a página Índice é chamada
IndexModel de .
O construtor usa injeção de dependência para adicionar o RazorPagesMovieContext à
página:

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

Confira Código assíncrono para obter mais informações sobre a programação


assíncrona com o Entity Framework.

Quando uma GET solicitação é feita para a página, o OnGetAsync método retorna uma
lista de filmes para a Razor Página. Em uma Razor Página, OnGetAsync ou OnGet é
chamado 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 , nenhuma instrução return é
usada. Por exemplo, examine a Privacy Página:

C#

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

namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;

public PrivacyModel(ILogger<PrivacyModel> logger)


{
_logger = logger;
}

public void OnGet()


{
}
}
}
Quando o tipo de retorno for IActionResult ou Task<IActionResult> , é necessário
fornecer uma instrução de retorno. Por exemplo, o Pages/Movies/Create.cshtml.cs
OnPostAsync método :

C#

public async Task<IActionResult> OnPostAsync()


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

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

return RedirectToPage("./Index");
}

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

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

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

Razor pode fazer a transição de HTML para C# ou para Razormarcação específica.


Quando um @ símbolo é seguido por uma Razor palavra-chave reservada, ele faz a
transição para Razoruma marcação específica, caso contrário, ele faz a transição para
C#.

A diretiva @page
A @page Razor diretiva torna o arquivo uma ação MVC, o que significa que ele pode lidar
com solicitações. @page deve ser a primeira diretiva do Razor em uma página. @page e
@model são exemplos de transição para Razormarcação específica. Consulte Razor

sintaxe para obter mais informações.

A diretiva @model
CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel
A @model diretiva especifica o tipo do modelo passado para a Razor Página. No exemplo
anterior, a @model linha disponibiliza a PageModel classe derivada para a Razor Página. O
modelo é usado nos auxiliares HTML @Html.DisplayNameFor e @Html.DisplayFor na
página.

Examine a expressão lambda usada no auxiliar HTML a seguir:

CSHTML

@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á violação de acesso quando
model , model.Movie ou model.Movie[0] está ou está null vazio. Quando a expressão
lambda é avaliada, por exemplo, com @Html.DisplayFor(modelItem => item.Title) , os
valores de propriedade do modelo são avaliados.

A página de layout
Selecione os links Razorde menu PagesMovie, Homee Privacy. Cada página mostra o
mesmo layout de menu. O layout do menu é implementado no
Pages/Shared/_Layout.cshtml arquivo.

Abra e examine o Pages/Shared/_Layout.cshtml arquivo.

Os modelos de layout permitem que o layout do contêiner HTML seja:

Especificado em um único lugar.


Aplicado a várias páginas no site.

Localize a linha @RenderBody() . RenderBody é um espaço reservado em que todas as


exibições específicas da página são mostradas, encapsuladas na página de layout. Por
exemplo, selecione o Privacy link e a exibição Pages/Privacy.cshtml é renderizada
dentro do RenderBody método .

ViewData e layout
Considere a seguinte marcação do Pages/Movies/Index.cshtml arquivo:

CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel

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

A marcação realçada anterior é um exemplo de Razor transição para C#. Os caracteres


{ e } circunscrevem um bloco de código C#.

A PageModel classe base contém uma ViewData propriedade de dicionário que pode ser
usada para passar dados para um Modo de Exibição. Os objetos são adicionados ao
ViewData dicionário usando um padrão de valor de chave . No exemplo anterior, a

propriedade Title é adicionada ao dicionário ViewData .

A Title propriedade é usada no Pages/Shared/_Layout.cshtml arquivo . A marcação a


seguir mostra as primeiras linhas do _Layout.cshtml arquivo.

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
<link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-
version="true" />

A linha @*Markup removed for brevity.*@ é um Razor comentário. Ao contrário dos


comentários <!-- --> HTML, Razor os comentários não são enviados ao cliente. Confira
Documentos da Web do MDN: Introdução ao HTML para obter mais informações.

Atualizar o layout
1. Altere o <title> elemento no Pages/Shared/_Layout.cshtml arquivo para exibir
Movie em vez de RazorPagesMovie.

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

2. Localize o seguinte elemento de âncora no Pages/Shared/_Layout.cshtml arquivo.

CSHTML

<a class="navbar-brand" asp-area="" asp-


page="/Index">RazorPagesMovie</a>

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

CSHTML

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

O elemento de âncora anterior é um Auxiliar de Marcas. Nesse caso, ele é o


Auxiliar de Marcas de Âncora. O asp-page="/Movies/Index" atributo e o valor do
Auxiliar de Marca criam um link para a /Movies/Index Razor Página. O valor do
atributo asp-area está vazio e, portanto, a área não é usada no link. Confira Áreas
para obter mais informações.

4. Salve as alterações e teste o aplicativo selecionando o link RpMovie . Confira o


arquivo _Layout.cshtml no GitHub caso tenha problemas.

5. Teste os Homelinks , RpMovie, Criar, Editar e Excluir . Cada página define o título,
que você pode ver na guia do navegador. Quando você marca uma página, o
título é usado para o indicador.

7 Observação

Talvez você não consiga inserir casas decimais ou vírgulas no campo Price . Para
dar suporte à validação jQuery para localidades não inglesas que usam uma
vírgula ("") para um ponto decimal e formatos de data não US-English, você deve
tomar medidas para globalizar o aplicativo. Confira Problema 4076 do GitHub
para obter instruções sobre como adicionar casas decimais.

A Layout propriedade é definida no Pages/_ViewStart.cshtml arquivo:


CSHTML

@{
Layout = "_Layout";
}

A marcação anterior define o arquivo de layout como Pages/Shared/_Layout.cshtml para


todos os Razor arquivos na pasta Páginas . Veja Layout para obter mais informações.

O modelo Criar página


Examine o modelo de Pages/Movies/Create.cshtml.cs página:

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext
_context;

public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext
context)
{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

[BindProperty]
public Movie Movie { get; set; } = default!;

// To protect from overposting attacks, see


https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie ==
null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

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

O método OnGet inicializa qualquer estado necessário para a página. A página Criar não
tem nenhum estado para inicializar, assim, Page é retornado. Apresentamos um
exemplo de inicialização de estado OnGet posteriormente no tutorial. O Page método
cria um PageResult objeto que renderiza a Create.cshtml página.

A Movie propriedade usa o atributo [BindProperty] para aceitar a associação de modelo.


Quando o formulário Criar posta os valores de formulário, o runtime do ASP.NET Core
associa os valores postados ao modelo Movie .

O método OnPostAsync é executado quando a página posta dados de formulário:

C#

public async Task<IActionResult> OnPostAsync()


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

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

return RedirectToPage("./Index");
}

Se há algum erro de modelo, o formulário é reexibido juntamente com quaisquer dados


de formulário postados. A maioria dos erros de modelo podem ser capturados no lado
do cliente antes do formulário ser enviado. Um exemplo de um erro de modelo é
postar, para o campo de data, um valor que não pode ser convertido em uma data. A
validação do lado do cliente e a validação de modelo são abordadas mais adiante no
tutorial.

Se não houver erros de modelo:

Os dados são salvos.


O navegador é redirecionado para a página Índice.
A página Criar Razor
Examine o Pages/Movies/Create.cshtml Razor arquivo de página:

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

O Visual Studio exibe as marcas a seguir em uma fonte em negrito diferente usada
em auxiliares de marcações:

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

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

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 Razor marcação para cada campo no modelo, exceto a
ID, semelhante à seguinte:

CSHTML

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


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

Os Auxiliares de Marca de Validação ( <div asp-validation-summary e <span asp-


validation-for ) exibem erros de validação. A validação será abordada em mais detalhes
posteriormente nesta série.

O Auxiliar de Marca de Rótulo ( <label asp-for="Movie.Title" class="control-label">


</label> ) gera a legenda do rótulo e [for] o atributo para a Title propriedade .

O Auxiliar de Marca de Entrada ( <input asp-for="Movie.Title" class="form-control"> )


usa os atributos DataAnnotations e produz atributos HTML necessários para a Validação
de jQuery no lado do cliente.

Para obter mais informações sobre Auxiliares de Marcas, como <form method="post"> ,
confira Auxiliares de Marcas no ASP.NET Core.

Recursos adicionais
Anterior: Adicionar um modelo Avançar: Trabalhar com um banco de dados
Parte 4 da série de tutoriais em Razor
Páginas
Artigo • 03/12/2022 • 22 minutos para o fim da leitura

Por Joe Audette

O objeto RazorPagesMovieContext cuida da tarefa de se conectar ao banco de dados e


mapear objetos Movie para registros do banco de dados. O contexto do banco de
dados é registrado com o contêiner injeção de dependência em Program.cs :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

O sistema de configuração de ASP.NET Core lê a ConnectionString chave. Para


desenvolvimento local, a configuração obtém a cadeia de conexão do appsettings.json
arquivo.

Visual Studio

A cadeia de conexão gerada é semelhante ao seguinte JSON:

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"RazorPagesMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=RazorPagesMovieContext-
bc;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Quando o aplicativo é implantado em um servidor de teste ou produção, uma variável


de ambiente pode ser usada para definir a cadeia de conexão como um servidor de
banco de dados de teste ou de produção. Para obter mais informações, confira
Configuração.

Visual Studio

SQL Server Express LocalDB


O LocalDB é uma versão leve do mecanismo de banco de dados do SQL Server
Express direcionada para o desenvolvimento de programas. O LocalDB é iniciado
sob demanda e executado no modo de usuário e, portanto, não há nenhuma
configuração complexa. Por padrão, o banco de dados LocalDB cria arquivos *.mdf
no diretório C:\Users\<user>\ .

1. No menu Exibir, abra SSOX (Pesquisador de Objetos do SQL Server).


2. Clique com o botão direito do mouse na Movie tabela 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.

3. Clique com o botão direito do mouse na Movie tabela e selecione Exibir


Dados:

Propagar o banco de dados


Crie uma classe chamada SeedData na pasta Models com o seguinte código:

C#
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;

namespace RazorPagesMovie.Models;

public static class SeedData


{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new RazorPagesMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<RazorPagesMovieContext>>()))
{
if (context == null || context.Movie == null)
{
throw new ArgumentNullException("Null
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 filmes no banco de dados, o inicializador de semente retornará e nenhum


filme será adicionado.

C#

if (context.Movie.Any())
{
return;
}

Adicionar o inicializador de semeadura


Atualize o Program.cs com o seguinte código realçado:

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

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


{
var services = scope.ServiceProvider;

SeedData.Initialize(services);
}
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

No código anterior, Program.cs foi modificado para fazer o seguinte:

Obtenha uma instância de contexto de banco de dados do contêiner de DI


(injeção de dependência).
Chame o seedData.Initialize método , passando para ele a instância de contexto
do banco de dados.
Descarte o contexto quando o método de semente for concluído. A instrução
using garante que o contexto seja descartado.

A seguinte exceção ocorre quando Update-Database não foi executada:

SqlException: Cannot open database "RazorPagesMovieContext-" requested by the


login. The login failed. Login failed for user 'user name'.

Testar o aplicativo
Exclua todos os registros no banco de dados para que o método de semente seja
executado. Interrompa e inicie o aplicativo para propagar o banco de dados. Se o banco
de dados não for propagado, coloque um ponto if (context.Movie.Any()) de
interrupção e percorra o código.

O aplicativo mostra os dados propagados:


Recursos adicionais
Anterior: Scaffolded Razor Páginas a seguir: Atualizar as páginas
Parte 5, atualize as páginas geradas em
um aplicativo ASP.NET Core
Artigo • 03/12/2022 • 15 minutos para o fim da leitura

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação
não é ideal. ReleaseDate deve ser duas palavras, Data de Lançamento.

Atualizar o modelo
Atualize Models/Movie.cs com o seguinte código realçado:

C#

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

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
}

No código anterior:

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.
O atributo [Display] especifica o nome de exibição de um campo. No código
anterior, Release Date em vez de ReleaseDate .
O atributo [DataType] especifica o tipo dos dados ( Date ). As informações de
tempo armazenadas no campo não são exibidas.

DataAnnotations é abordado no próximo tutorial.

Navegue até Páginas/Filmes e passe o mouse sobre um link Editar para ver a URL de
destino.
Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marca de Âncora no
Pages/Movies/Index.cshtml arquivo.

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 Auxiliar de Marca de Âncora gera dinamicamente o valor do


atributo HTML href da Razor Página (a rota é relativa), o asp-page e o identificador de
rota (). asp-route-id Para obter mais informações, consulte Geração de URL para
Páginas.

Use Exibir Fonte de um navegador para examinar a marcação gerada. Uma parte do
HTML gerado é mostrada abaixo:

HTML

<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 do filme com uma cadeia de caracteres de
consulta . Por exemplo, o ?id=1 em https://localhost:5001/Movies/Details?id=1 .

Adicionar modelo de rota


Atualize as páginas Editar, Detalhes e Excluir Razor para usar o {id:int} modelo de
rota. 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:

HTML

<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 {id:int} modelo de rota que não inclui o inteiro
retorna um erro HTTP 404 (não encontrado). Por exemplo,
https://localhost:5001/Movies/Details retorna um erro 404. Para tornar a ID opcional,
acrescente ? à restrição de rota:

CSHTML

@page "{id:int?}"

Teste o comportamento de @page "{id:int?}" :

1. Defina a diretiva de página em como Pages/Movies/Details.cshtml @page "


{id:int?}" .

2. Defina um ponto de interrupção em public async Task<IActionResult>


OnGetAsync(int? id) , em Pages/Movies/Details.cshtml.cs .

3. Navegue até https://localhost:5001/Movies/Details/ .

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 OnGetAsync
método retorna NotFound (HTTP 404):

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

Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

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

Examinar o tratamento de exceção de simultaneidade


Examine o OnPostAsync método no Pages/Movies/Edit.cshtml.cs arquivo:

C#

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
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");
}

private bool MovieExists(int id)


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

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 :

1. Defina um ponto de interrupção em catch (DbUpdateConcurrencyException)


2. Selecione Editar para um filme, faça alterações, mas não insira Salvar.
3. Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em
seguida, exclua o filme.
4. 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 Pages/Movies/Edit.cshtml.cs arquivo:

C#

public class EditModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

[BindProperty]
public Movie Movie { get; set; } = default!;

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


{
if (id == null || _context.Movie == null)
{
return NotFound();
}

var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id ==


id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}

// To protect from overposting attacks, enable the specific properties


you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
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");
}

private bool MovieExists(int id)


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

Quando uma solicitação HTTP GET é feita na página Filmes/Editar, por exemplo,
https://localhost:5001/Movies/Edit/3 :

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


O Page método renderiza a Pages/Movies/Edit.cshtml Razor Página. O
Pages/Movies/Edit.cshtml arquivo contém a diretiva @model
RazorPagesMovie.Pages.Movies.EditModel de modelo , que disponibiliza o modelo

de filme na página.
O formulário Editar é exibido com os valores do filme.

Quando a página Movies/Edit é postada:


Os valores de formulário na página são associados à propriedade Movie . O
atributo [BindProperty] habilita o Model binding.

C#

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

Se houver erros no estado do modelo, por exemplo, ReleaseDate não poderão ser
convertidos em uma data, o formulário será reproduzido com os valores enviados.

Se não houver erros do modelo, o filme será salvo.

Os métodos HTTP GET nas páginas Index, Create e Delete Razor seguem um padrão
semelhante. O método HTTP POST OnPostAsync na página Criar Razor segue um padrão
semelhante ao OnPostAsync método na Página de Edição Razor .

Recursos adicionais
Anterior: Trabalhar com um banco de dados Avançar: Adicionar pesquisa
Parte 6, adicionar pesquisa ao ASP.NET
Core Razor Pages
Artigo • 10/01/2023 • 10 minutos para o fim da leitura

De Rick Anderson

Nas seções a seguir, a pesquisa de filmes por gênero ou nome é adicionada.

Adicione o código realçado a seguir a Pages/Movies/Index.cshtml.cs :

C#

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

namespace RazorPagesMovie.Pages.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext
_context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext
context)
{
_context = context;
}

public IList<Movie> Movie { get;set; } = default!;


[BindProperty(SupportsGet = true)]
public string ? SearchString { get; set; }
public SelectList ? Genres { get; set; }
[BindProperty(SupportsGet = true)]
public string ? MovieGenre { get; set; }

No código anterior:

SearchString : contém o texto que os usuários inserem na caixa de texto de


pesquisa. SearchString tem o [BindProperty] atributo . [BindProperty] associa
valores de formulário e cadeias de consulta ao mesmo nome da propriedade.
[BindProperty(SupportsGet = true)] é necessário para associação em solicitações

HTTP 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 selecionado pelo usuário. Por exemplo,

"Western".
Genres e MovieGenre são abordados mais adiante neste tutorial.

2 Aviso

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 a solicitações GET , defina a propriedade


SupportsGet do atributo [BindProperty] como true :

C#

[BindProperty(SupportsGet = true)]

Para obter mais informações, confira ASP.NET Core Community Standup: Bind on
GET discussion (YouTube) .

Atualize o método OnGetAsync da página de Índice pelo seguinte código:

C#

public async Task OnGetAsync()


{
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}

Movie = await movies.ToListAsync();


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

C#

// using System.Linq;
var movies = from m in _context.Movie
select m;

A consulta só é definida neste ponto, 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:

C#

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 operador de
consulta padrão, como o método Where ou Contains . As consultas LINQ não são
executadas quando são definidas ou quando são modificadas chamando um método,
como Where , Contains ou OrderBy . Em vez disso, a execução da consulta é adiada. A
avaliação de uma expressão é atrasada até que seu valor realizado seja iterado ou o
ToListAsync método seja chamado. Consulte Execução de consulta para obter mais

informações.

7 Observação

O Contains método é 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. SQLite com a ordenação padrão é uma
combinação de diferenciação de maiúsculas de minúsculas emaiúsculas de
minúsculas, dependendo da consulta. Para obter informações sobre como tornar as
consultas SQLite que não diferenciam maiúsculas de minúsculas, consulte o
seguinte:

Este problema do GitHub


Este problema do GitHub
Ordenações e diferenciação de maiúsculas e minúsculas

Navegue até a página Filmes e acrescente uma cadeia de caracteres 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 Índice, a cadeia de caracteres de


pesquisa poderá ser passada como um segmento de URL. Por exemplo,
https://localhost:5001/Movies/Ghost .

CSHTML

@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 runtime 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 ). A associação de modelo não diferencia


maiúsculas de minúsculas.

No entanto, não é esperado 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 Pages/Movies/Index.cshtml arquivo e adicione a marcação realçada no seguinte


código:

CSHTML

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

A marca <form> HTML usa os seguintes Auxiliares de Marcas:

Auxiliar de Marca de Formulário. Quando o formulário é enviado, a cadeia de


caracteres de filtro é enviada para a página Pages/Movies/Index por meio da cadeia
de consulta.
Auxiliar de marcação de entrada

Salve as alterações e teste o filtro.

Pesquisar por gênero


Atualize o método OnGetAsync da página de Índice pelo seguinte código:

C#

public async Task OnGetAsync()


{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

var movies = from m in _context.Movie


select m;

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

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

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

C#

// Use LINQ to get list of genres.


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

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

C#

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

Adicionar pesquisa por gênero à Razor Página


Atualize o Index.cshtml <form> elemento conforme realçado na seguinte marcação:

CSHTML

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

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

Anterior: Atualizar as páginas Avançar: Adicionar um novo campo


Parte 7, adicione um novo campo a uma
Razor Página no ASP.NET Core
Artigo • 02/12/2022 • 24 minutos para o fim da leitura

De Rick Anderson

Nesta seção, as Migrações do Entity Framework Code First são usadas para:

Adicionar um novo campo ao modelo.


Migrar a nova alteração de esquema de campo para o banco de dados.

Ao usar o EF Code First para criar e acompanhar automaticamente um banco de dados,


Code First:

Adiciona uma __EFMigrationsHistory tabela ao banco de dados para acompanhar


se o esquema do banco de dados está em sincronia com as classes de modelo das
quais ele foi gerado.
Gerará uma exceção se as classes de modelo não estiverem sincronizadas com o
banco de dados.

A verificação automática de que o esquema e o modelo estão em sincronia facilita a


localização de problemas de código de banco de dados inconsistentes.

Adicionando uma propriedade de classificação


ao modelo de filme
1. Abra o Models/Movie.cs arquivo e adicione uma Rating propriedade:

C#

public class Movie


{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;

[Display(Name = "Release Date")]


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

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
public string Rating { get; set; } = string.Empty;
}
2. Edite Pages/Movies/Index.cshtml e adicione um Rating campo:

CSHTML

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

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

3. Atualize as seguintes páginas com um Rating campo:

Pages/Movies/Create.cshtml .
Pages/Movies/Delete.cshtml .
Pages/Movies/Details.cshtml .
Pages/Movies/Edit.cshtml .

O aplicativo não funcionará até que o banco de dados seja atualizado para incluir o
novo campo. A execução do aplicativo sem uma atualização no banco de dados lança
um SqlException :

SqlException: Invalid column name 'Rating'.

A SqlException exceção é causada pela classe de modelo Movie atualizada ser diferente
do esquema da tabela Movie do banco de dados. Não há nenhuma Rating coluna na
tabela do 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 os
desenvolvedores evoluam rapidamente o modelo e o esquema de banco de dados
juntos. A desvantagem é que os dados existentes no banco de dados são perdidos.
Não use essa abordagem em um banco de dados de produção. Descartar o banco
de dados em alterações de esquema e usar um inicializador para propagar
automaticamente o banco de dados com dados de teste geralmente é 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 é manter 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 exemplo é mostrada abaixo, mas faça essa alteração para cada new Movie
bloco.

C#

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

Consulte o arquivo SeedData.cs concluído .

Compile a solução.

Visual Studio

Adicionar uma migração para o campo de classificação


1. No menu Ferramentas , selecione Console do Gerenciador de Pacotes do
Gerenciador de Pacotes > NuGet.
2. No PMC, insira os seguintes comandos:

PowerShell

Add-Migration Rating
Update-Database

O comando Add-Migration informa à estrutura:

Compare o Movie modelo com o esquema de Movie banco de dados.


Crie código para migrar o esquema de banco de dados 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.

O Update-Database comando informa à estrutura para aplicar as alterações de


esquema ao banco de dados e preservar os dados existentes.

Exclua todos os registros no banco de dados, o inicializador propagará o banco de


dados e incluirá o Rating campo . A exclusão pode ser feita com os links de
exclusão no navegador ou no Sql Server Pesquisador de Objetos (SSOX).

Outra opção é excluir o banco de dados e usar as migrações para recriar o banco
de dados. Para excluir o banco de dados no SSOX:

1. Selecione o banco de dados no SSOX.

2. Clique com o botão direito do mouse no banco de dados e selecione Excluir.

3. Marque Fechar conexões existentes.

4. Selecione OK.

5. No PMC, atualize o banco de dados:

PowerShell

Update-Database

Execute o aplicativo e verifique se você pode criar, editar e exibir filmes com um Rating
campo. Se o banco de dados não for propagado, defina um ponto de interrupção no
método SeedData.Initialize .
Recursos adicionais
Anterior: Adicionar Pesquisa Avançar: Adicionar Validação
Parte 8 da série de tutoriais no Razor
Pages
Artigo • 03/12/2022 • 31 minutos para o fim da leitura

De Rick Anderson

Nesta seção, a lógica de validação é adicionada para o modelo Movie . As regras de


validação são impostas sempre que um usuário cria ou edita um filme.

Validação
Um princípio-chave do desenvolvimento de software é chamado DRY (“Don't Repeat
Yourself”). Razor O Pages incentiva o desenvolvimento em que a funcionalidade é
especificada uma vez e é 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 à validação fornecido pelo Pages e pelo Razor Entity Framework é um bom
exemplo do princípio DRY:

As regras de validação são especificadas declarativamente em um só lugar, na


classe de modelo.
As regras são impostas em todos os lugares do aplicativo.

Adicionar regras de validação ao modelo de


filme
O System.ComponentModel.DataAnnotations namespace fornece:

Um conjunto de atributos de validação internos que são aplicados


declarativamente a uma classe ou propriedade.
A formatação de atributos como [DataType] esse ajuda na formatação e não
fornece nenhuma validação.

Atualize a classe Movie para aproveitar os atributos de validação [Required] ,


[StringLength] , [RegularExpression] e [Range] internos.

C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; } = string.Empty;

// [Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { 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; } = string.Empty;

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

Os atributos de validação especificam o comportamento a ser imposto nas


propriedades de modelo às quais eles são aplicados:

Os atributos [Required] e [MinimumLength] indicam que uma propriedade deve ter


um valor. Nada impede que um usuário insira espaço em branco para atender a
essa validação.

O atributo [RegularExpression] é usado para limitar quais caracteres podem ser


inseridos. No código anterior, Genre :
Deve usar apenas letras.
A primeira letra deve ser maiúscula. Espaços em branco são permitidos
enquanto números e caracteres especiais não são permitidos.

O RegularExpression Rating :
Exige que o primeiro caractere seja uma letra maiúscula.
Permite caracteres especiais e números nos espaços subsequentes. "PG-13" é
válido para uma classificação, mas falha para um Genre .

O atributo [Range] restringe um valor a um intervalo especificado.

O [StringLength] atributo pode definir um comprimento máximo de uma


propriedade de cadeia de caracteres e, opcionalmente, seu comprimento mínimo.

Tipos de valor, como decimal , int , float , DateTime , são inerentemente


necessários e não precisam do [Required] atributo .

As regras de validação anteriores são usadas para demonstração, elas não são ideais
para um sistema de produção. Por exemplo, o anterior impede a entrada de um filme
com apenas dois caracteres e não permite caracteres especiais em Genre .

Ter regras de validação automaticamente impostas pelo ASP.NET Core ajuda a:

Torne o aplicativo mais robusto.


Reduza as chances de salvar dados inválidos no banco de dados.

Interface do usuário do erro de validação em Razor


páginas
Execute o aplicativo e navegue para Pages/Movies.

Selecione o link Criar Novo . Preencha o formulário com alguns valores inválidos.
Quando a validação do lado do cliente do jQuery detecta o erro, ela exibe uma
mensagem de erro.
7 Observação

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. Consulte
este comentário do GitHub 4076 para obter instruções sobre como adicionar
vírgula decimal.

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 JavaScript e jQuery e no lado do servidor, quando um usuário tem o
JavaScript desabilitado.

Um benefício significativo é que nenhuma alteração de código foi necessária nas


páginas Criar ou Editar. Depois que as anotações de dados foram aplicadas ao modelo,
a interface do usuário de validação foi habilitada. As Razor Páginas criadas neste tutorial
pegaram automaticamente as regras de validação usando atributos de validação nas
propriedades da Movie classe de modelo. 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


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

1. Desabilite o JavaScript no navegador. O JavaScript pode ser desabilitado usando as


ferramentas de desenvolvedor do navegador. Se o JavaScript não puder ser
desabilitado no navegador, tente outro navegador.

2. Defina um ponto de interrupção no método OnPostAsync da página Criar ou Editar.

3. Envie um formulário com dados inválidos.

4. Verifique se o estado do modelo é inválido:


C#

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

Como alternativa, desabilite a validação do lado do cliente no servidor.

O código a seguir mostra uma parte da Create.cshtml página estruturada


anteriormente no tutorial. Ele é usado pelas páginas Criar e Editar para:

Exiba o formulário inicial.


Reproduz o formulário em caso de erro.

CSHTML

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

O Auxiliar de Marcação de Entrada usa os atributos de DataAnnotations e produz os


atributos HTML necessários para a Validação do jQuery no lado do cliente. O Auxiliar de
Marcação de Validação exibe erros de validação. Consulte Validação para obter mais
informações.

As páginas Criar e Editar não têm nenhuma regra de validação. As regras de validação e
as cadeias de caracteres de erro são especificadas somente na classe Movie . Essas regras
de validação são aplicadas automaticamente às Razor Páginas que editam o Movie
modelo.

Quando a lógica de validação precisa ser alterada, ela é feita apenas no modelo. A
validação é aplicada consistentemente em todo o aplicativo, a lógica de validação é
definida em um só lugar. A validação em um único lugar ajuda a manter o código limpo
e facilita sua manutenção e atualização.

Usar atributos DataType


Examine a classe Movie . O namespace System.ComponentModel.DataAnnotations fornece
atributos de formatação, além do conjunto interno de atributos de validação. O atributo
[DataType] é aplicado às propriedades ReleaseDate e Price .

C#

// [Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

Os [DataType] atributos fornecem:

Dicas para o mecanismo de exibição formatar os dados.


Fornece atributos como <a> para URLs e <a href="mailto:EmailAddress.com">
para 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. [DataType] atributos não são atributos de validação. No


aplicativo de exemplo, somente a data é exibida, sem hora.

A DataType enumeração fornece muitos tipos de dados, como Date , Time , PhoneNumber ,
Currency , EmailAddress e muito mais.

Os [DataType] atributos:

Pode habilitar o aplicativo para fornecer automaticamente recursos específicos de


tipo. Por exemplo, um link mailto: pode ser criado para DataType.EmailAddress .
Pode fornecer um seletor DataType.Date de data em navegadores que dão suporte
a HTML5.
Emita HTML 5 data- , pronunciado "data dash", atributos que os navegadores
HTML 5 consomem.
Não forneça 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:

C#

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


true)]
public DateTime ReleaseDate { get; set; }

A ApplyFormatInEditMode configuração especifica que a formatação será aplicada


quando o valor for exibido para edição. Esse comportamento pode não ser desejado
para alguns campos. Por exemplo, em valores de moeda, o símbolo de moeda
geralmente não é desejado 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 em
vez de como renderizá-los em uma tela. O [DataType] atributo fornece os seguintes
benefícios que não estão disponíveis com [DisplayFormat] :

O navegador pode habilitar recursos HTML5, por exemplo, para mostrar um


controle de calendário, o símbolo de moeda apropriado à localidade, links de
email etc.
Por padrão, o navegador renderiza dados usando o formato correto com base em
sua 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 o [Range] atributo 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:

C#

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

É uma prática recomendada evitar a compilação de datas difíceis em modelos, portanto,


usar o [Range] atributo e DateTime não é recomendável. Use Configuração para
intervalos de datas e outros valores que estão sujeitos a alterações frequentes em vez
de especificá-la no código.

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

C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


public string Title { get; set; } = string.Empty;

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


public DateTime ReleaseDate { get; set; }

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


public string Genre { get; set; } = string.Empty;

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


[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

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

Introdução ao Razor Páginas e EF Core mostra operações avançadas EF Core com Razor
o Pages.

Aplicar migrações
As DataAnnotations aplicadas à classe alteram o esquema. Por exemplo, as
DataAnnotations aplicadas ao campo Title :

C#

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; } = string.Empty;

Limitam os caracteres a 60.


Não permitem um valor null .

Atualmente a tabela Movie tem o seguinte esquema:

SQL
CREATE TABLE [dbo].[Movie] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (MAX) NULL,
[ReleaseDate] DATETIME2 (7) NOT NULL,
[Genre] NVARCHAR (MAX) NULL,
[Price] DECIMAL (18, 2) NOT NULL,
[Rating] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED ([ID] ASC)
);

As alterações do esquema anterior não fazem com que o EF lance uma exceção. No
entanto, crie uma migração de forma que o esquema seja consistente com o modelo.

Visual Studio

No menu Ferramentas , selecione Console do Gerenciador de Pacotes do


Gerenciador de Pacotes > NuGet. No PMC, insira os seguintes comandos:

PowerShell

Add-Migration New_DataAnnotations
Update-Database

Update-Database executa os métodos Up da classe New_DataAnnotations . Examine o


método Up :

C#

public partial class NewDataAnnotations : Migration


{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Movie",
type: "nvarchar(60)",
maxLength: 60,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");

migrationBuilder.AlterColumn<string>(
name: "Rating",
table: "Movie",
type: "nvarchar(5)",
maxLength: 5,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");

migrationBuilder.AlterColumn<string>(
name: "Genre",
table: "Movie",
type: "nvarchar(30)",
maxLength: 30,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
}

A tabela Movie atualizada tem o seguinte esquema:

SQL

CREATE TABLE [dbo].[Movie] (


[ID] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (60) NOT NULL,
[ReleaseDate] DATETIME2 (7) NOT NULL,
[Genre] NVARCHAR (30) NOT NULL,
[Price] DECIMAL (18, 2) NOT NULL,
[Rating] NVARCHAR (5) NOT NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED ([ID] ASC)
);

Publicar no Azure
Para obter informações sobre como implantar no Azure, confira Tutorial: Criar um
aplicativo ASP.NET Core no Azure com Banco de Dados SQL.

Obrigado por concluir esta introdução ao Razor Pages. Introdução ao Razor Pages e EF
Core é um excelente acompanhamento para este tutorial.

Recursos adicionais
Auxiliares de marcação 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

Anterior: adicionar um novo campo


Filtrar métodos para Razor Páginas no
ASP.NET Core
Artigo • 10/01/2023 • 9 minutos para o fim da leitura

De Rick Anderson

Razor Filtros IPageFilter de página e IAsyncPageFilter permitir que Razor Pages


executem código antes e depois que um Razor manipulador de página é executado.
RazorOs filtros de página são semelhantes a ASP.NET Core filtros de ação MVC, exceto
que não podem ser aplicados a métodos individuais de manipulador de página.

Razor Filtros de página:

Executam o código depois que um método do manipulador é selecionado, mas


antes que o model binding ocorra.
Executam o código antes que o método do manipulador seja executado, após a
conclusão do model binding.
Executam o código após a execução do método do manipulador.
Podem ser implementados em uma única página ou globalmente.
Não podem ser aplicados a métodos do manipulador de uma página específica.
Pode ter dependências de construtor preenchidas pela DI ( Injeção de
Dependência ). Para obter mais informações, consulte ServiceFilterAttribute e
TypeFilterAttribute.

Embora os construtores de página e o middleware habilitem a execução de código


personalizado antes da execução de um método de manipulador, somente Razor os
filtros de página habilitam o acesso e HttpContext a página. O middleware tem acesso
ao HttpContext , mas não ao "contexto de página". Os filtros têm um FilterContext
parâmetro derivado, que fornece acesso a HttpContext . Aqui está um exemplo para um
filtro de página: implemente um atributo de filtro que adiciona um cabeçalho à
resposta, algo que não pode ser feito com construtores ou middleware. O acesso ao
contexto da página, que inclui o acesso às instâncias da página e seu modelo, só está
disponível ao executar filtros, manipuladores ou o corpo de uma Razor Página.

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

Razor Os filtros de página fornecem os seguintes métodos, que podem ser aplicados
globalmente ou no nível da página:

Métodos síncronos:
OnPageHandlerSelected : chamado após a seleção de um método de
manipulador, mas antes da associação de modelo ocorrer.
OnPageHandlerExecuting : chamado antes da execução do método de
manipulador, após a conclusão da associação de modelo.
OnPageHandlerExecuted : chamado após a execução do método de
manipulador, antes do resultado da ação.

Métodos assíncronos:
OnPageHandlerSelectionAsync : chamado de forma assíncrona depois que o
método de manipulador tiver sido selecionado, mas antes que a associação de
modelo ocorra.
OnPageHandlerExecutionAsync : chamado de forma assíncrona antes que o
método de manipulador seja invocado, após a conclusão da associação de
modelo.

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
ambas as interfaces forem implementadas, somente os métodos assíncronos serão
chamados. A mesma regra aplica-se para substituições em páginas. Implemente a
versão síncrona ou a assíncrona da substituição, não ambas.

Implementar Razor filtros de página


globalmente
O código a seguir implementa IAsyncPageFilter :

C#

public class SampleAsyncPageFilter : IAsyncPageFilter


{
private readonly IConfiguration _config;

public SampleAsyncPageFilter(IConfiguration config)


{
_config = config;
}

public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext


context)
{
var key = _config["UserAgentID"];
context.HttpContext.Request.Headers.TryGetValue("user-agent",
out StringValues
value);
ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,

"SampleAsyncPageFilter.OnPageHandlerSelectionAsync",
value, key.ToString());

return Task.CompletedTask;
}

public async Task


OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,

PageHandlerExecutionDelegate next)
{
// Do post work.
await next.Invoke();
}
}

No código anterior, ProcessUserAgent.Write é fornecido pelo usuário o código que


funciona com a cadeia de caracteres do agente do usuário.

O código a seguir habilita o SampleAsyncPageFilter na classe Startup :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new SampleAsyncPageFilter(Configuration));
});
}

O código a seguir chama AddFolderApplicationModelConvention para aplicar o


SampleAsyncPageFilter a apenas páginas em /Movies:

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddRazorPages(options =>
{
options.Conventions.AddFolderApplicationModelConvention(
"/Movies",
model => model.Filters.Add(new
SampleAsyncPageFilter(Configuration)));
});
}
O código a seguir implementa o IPageFilter síncrono:

C#

public class SamplePageFilter : IPageFilter


{
private readonly IConfiguration _config;

public SamplePageFilter(IConfiguration config)


{
_config = config;
}

public void OnPageHandlerSelected(PageHandlerSelectedContext context)


{
var key = _config["UserAgentID"];
context.HttpContext.Request.Headers.TryGetValue("user-agent", out
StringValues value);
ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
"SamplePageFilter.OnPageHandlerSelected",
value, key.ToString());
}

public void OnPageHandlerExecuting(PageHandlerExecutingContext context)


{
Debug.WriteLine("Global sync OnPageHandlerExecuting called.");
}

public void OnPageHandlerExecuted(PageHandlerExecutedContext context)


{
Debug.WriteLine("Global sync OnPageHandlerExecuted called.");
}
}

O código a seguir habilita o SamplePageFilter :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new SamplePageFilter(Configuration));
});
}

Implementar Razor filtros de página


substituindo métodos de filtro
O código a seguir substitui os filtros de página assíncrona Razor :

C#

public class IndexModel : PageModel


{
private readonly IConfiguration _config;

public IndexModel(IConfiguration config)


{
_config = config;
}

public override Task


OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
Debug.WriteLine("/IndexModel OnPageHandlerSelectionAsync");
return Task.CompletedTask;
}

public async override Task


OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,

PageHandlerExecutionDelegate next)
{
var key = _config["UserAgentID"];
context.HttpContext.Request.Headers.TryGetValue("user-agent", out
StringValues value);
ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
"/IndexModel-OnPageHandlerExecutionAsync",
value, key.ToString());

await next.Invoke();
}
}

Implementar um atributo de filtro


O filtro interno baseado em OnResultExecutionAsync atributo pode ser subclasse. O
filtro a seguir adiciona um cabeçalho à resposta:

C#

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;

namespace PageFilter.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;

public AddHeaderAttribute (string name, string value)


{
_name = name;
_value = value;
}

public override void OnResultExecuting(ResultExecutingContext


context)
{
context.HttpContext.Response.Headers.Add(_name, new string[] {
_value });
}
}
}

O código a seguir se aplica ao atributo AddHeader :

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using PageFilter.Filters;

namespace PageFilter.Movies
{
[AddHeader("Author", "Rick")]
public class TestModel : PageModel
{
public void OnGet()
{

}
}
}

Use uma ferramenta como as ferramentas de desenvolvedor do navegador para


examinar os cabeçalhos. Em Cabeçalhos de Resposta, author: Rick é exibido.

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.

Autorizar o atributo de filtro


O atributo Authorize pode ser aplicado a um PageModel :
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace PageFilter.Pages
{
[Authorize]
public class ModelWithAuthFilterModel : PageModel
{
public IActionResult OnGet() => Page();
}
}
Convenções de rota e aplicativo do
Razor Pages no ASP.NET Core
Artigo • 10/01/2023 • 38 minutos para o fim da leitura

Saiba como usar convenções de provedor de modelo de aplicativo e rota de página


para controlar o roteamento, a descoberta e o processamento de páginas em Razor
aplicativos Pages.

Para especificar uma rota de página, adicionar segmentos de rota ou adicionar


parâmetros a uma rota, use a diretiva da @page página. Para obter mais informações,
consulte Rotas personalizadas.

Há palavras reservadas que não podem ser usadas como segmentos de rota ou nomes
de parâmetro. Para obter mais informações, consulte Roteamento: nomes de
roteamento reservados.

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

Cenário O exemplo demonstra

Convenções de modelo Adicione um cabeçalho e um modelo de rota às


páginas de um aplicativo.
Conventions.Add

IPageRouteModelConvention
IPageApplicationModelConvention
IPageHandlerModelConvention

Convenções de ação da rota de página Adicione um modelo de rota às páginas em uma


pasta e a uma única página.
AddFolderRouteModelConvention
AddPageRouteModelConvention
AddPageRoute

Convenções de ação do modelo de página Adicione um cabeçalho às páginas em uma


pasta, adicione um cabeçalho a uma única
AddFolderApplicationModelConvention página e configure um alocador de filtro para
AddPageApplicationModelConvention adicionar um cabeçalho às páginas de um
ConfigureFilter (classe de filtro, aplicativo.
expressão lambda ou fábrica de filtros)

Razor As convenções de páginas são configuradas usando uma AddRazorPages


sobrecarga que configura RazorPagesOptions. Os exemplos de convenção a seguir
estão descritos mais adiante neste tópico:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
options.Conventions.Add( ... );
options.Conventions.AddFolderRouteModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageRouteModelConvention(
"/About", model => { ... });
options.Conventions.AddPageRoute(
"/Contact", "TheContactPage/{text?}");
options.Conventions.AddFolderApplicationModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageApplicationModelConvention(
"/About", model => { ... });
options.Conventions.ConfigureFilter(model => { ... });
options.Conventions.ConfigureFilter( ... );
});
}

Ordem de rota
As rotas especificam um Order para processamento (correspondência de rotas).

Ordem Comportamento
de rota

-1 A rota é processada antes que outras rotas sejam processadas.

0 A ordem não é especificada (valor padrão). Não atribuir Order ( Order = null ) usa
como padrão a rota Order como 0 (zero) para processamento.

1, 2, … n Especifica a ordem de processamento de rota.

O processamento de rotas é estabelecido por convenção:

As rotas são processadas em ordem sequencial (-1, 0, 1, 2, ... n).


Quando as rotas têm o mesmo Order , a rota mais específica é correspondida
primeiro seguida por rotas menos específicas.
Quando as rotas com o mesmo Order e o mesmo número de parâmetros
correspondem a uma URL de solicitação, as rotas são processadas na ordem em
que são adicionadas ao PageConventionCollection.
Se possível, evite depender de uma ordem de processamento de rota estabelecida. Em
geral, o roteamento seleciona a rota correta com a correspondência de URL. Se você
precisar definir propriedades de rota Order para rotear solicitações corretamente, o
esquema de roteamento do aplicativo provavelmente será confuso para os clientes e
frágil de manter. Busque simplificar o esquema de roteamento do aplicativo. O
aplicativo de exemplo requer uma ordem de processamento de rota explícita para
demonstrar vários cenários de roteamento usando um único aplicativo. No entanto,
você deve tentar evitar a prática de configurar a rota Order em aplicativos de produção.

Razor O roteamento de páginas e o roteamento do controlador MVC compartilham


uma implementação. As informações sobre a ordem de rota nos tópicos do MVC estão
disponíveis em Roteamento para ações do controlador: Ordenando rotas de atributo.

Convenções de modelo
Adicione um delegado para IPageConvention para adicionar convenções de modelo que
se aplicam ao Razor Pages.

Adicionar uma convenção de modelo de rota a todas as


páginas
Use Conventions para criar e adicionar um IPageRouteModelConvention à coleção de
IPageConvention instâncias que são aplicadas durante a construção do modelo de rota
de página.

O aplicativo de exemplo contém a GlobalTemplatePageRouteModelConvention classe para


adicionar um {globalTemplate?} modelo de rota a todas as páginas no aplicativo:

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace SampleApp.Conventions;

public class GlobalTemplatePageRouteModelConvention :


IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{globalTemplate?}"),
}
});
}
}
}

No código anterior:

O PageRouteModel é passado para o Apply método .


O PageRouteModel.Selectors obtém a contagem de seletores.
Um novo SelectorModel é adicionado, que contém um AttributeRouteModel

Razor As opções de páginas, como adicionar Conventions, são adicionadas quando


Razor Pages é adicionado à coleção de serviços. Para obter um exemplo, confira o
aplicativo de exemplo .

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.EntityFrameworkCore;
using SampleApp.Conventions;
using SampleApp.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>

options.UseInMemoryDatabase("InMemoryDb"));

builder.Services.AddRazorPages(options =>
{
options.Conventions.Add(new
GlobalTemplatePageRouteModelConvention());

options.Conventions.AddFolderRouteModelConvention("/OtherPages",
model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{otherPagesTemplate?}"),
}
});
}
});

options.Conventions.AddPageRouteModelConvention("/About", model =>


{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{aboutTemplate?}"),
}
});
}
});

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();

Considere a classe GlobalTemplatePageRouteModelConvention :

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace SampleApp.Conventions;

public class GlobalTemplatePageRouteModelConvention :


IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{globalTemplate?}"),
}
});
}
}
}

A propriedade Order do AttributeRouteModel é definida como 1 . Isso garante o


seguinte comportamento de correspondência de rotas no aplicativo de exemplo:

Um modelo de rota para TheContactPage/{text?} é adicionado posteriormente


neste tópico. A Contact Page rota tem uma ordem padrão de null ( Order = 0 ),
portanto, corresponde antes do {globalTemplate?} modelo de rota que tem Order
= 1.

O {aboutTemplate?} modelo de rota é mostrado no código anterior. 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 .

O {otherPagesTemplate?} modelo de rota é mostrado no código anterior. O


modelo {otherPagesTemplate?} recebe uma Order de 2 . Quando qualquer página
na pasta Pages/OtherPages é solicitada com um parâmetro de rota:

Por exemplo, /OtherPages/Page1/xyz

O valor "xyz" dos dados de rota é carregado em


RouteData.Values["globalTemplate"] ( Order = 1 ).

RouteData.Values["otherPagesTemplate"] com ( Order = 2 ) não é carregado devido

à Order propriedade 2 ter um valor mais alto.


Quando possível, não defina o Order . Quando Order não está definido, o padrão Order
= 0 é . Dependa do roteamento para selecionar a rota correta em vez da Order
propriedade .

Solicite a página do About exemplo em localhost:{port}/About/GlobalRouteValue e


inspecione o resultado:

O aplicativo de exemplo usa o pacote NuGet Rick.Docs.Samples.RouteInfo para exibir


informações de roteamento na saída de log. Usando localhost:
{port}/About/GlobalRouteValue , o agente exibe a solicitação, o Order e o modelo

usados:

CLI do .NET

info: SampleApp.Pages.AboutModel[0]
/About/GlobalRouteValue Order = 1 Template =
About/{globalTemplate?}

Adicionar uma convenção de modelo de aplicativo a


todas as páginas
Use Conventions para criar e adicionar um IPageApplicationModelConvention à coleção
de IPageConvention instâncias que são aplicadas durante a construção do modelo de
aplicativo de página.

Para demonstrar isso e outras convenções mais adiante no tópico, o aplicativo de


exemplo inclui uma classe AddHeaderAttribute . O construtor de classe aceita uma cadeia
de caracteres name e uma matriz de cadeia de caracteres values . Esses valores são
usados em seu método OnResultExecuting para definir um cabeçalho de resposta. A
classe completa é mostrada na seção Convenções de ação do modelo de página mais
adiante no tópico.

O aplicativo de exemplo usa a classe AddHeaderAttribute para adicionar um cabeçalho,


GlobalHeader , a todas as páginas no aplicativo:

C#

public class GlobalHeaderPageApplicationModelConvention


: IPageApplicationModelConvention
{
public void Apply(PageApplicationModel model)
{
model.Filters.Add(new AddHeaderAttribute(
"GlobalHeader", new string[] { "Global Header Value" }));
}
}

Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb"));

builder.Services.AddRazorPages(options =>
{
options.Conventions.Add(new
GlobalTemplatePageRouteModelConvention());

options.Conventions.Add(new
GlobalHeaderPageApplicationModelConvention());

Solicite a página About da amostra em localhost:{port}/About e inspecione os


cabeçalhos para exibir o resultado:
Adicionar uma convenção de modelo de manipulador a
todas as páginas
Use Conventions para criar e adicionar um IPageHandlerModelConvention à coleção de
IPageConvention instâncias que são aplicadas durante a construção do modelo de
manipulador de página.

C#

public class GlobalPageHandlerModelConvention


: IPageHandlerModelConvention
{
public void Apply(PageHandlerModel model)
{
// Access the PageHandlerModel
}
}

Convenções de ação da rota de página


O provedor de modelo de rota padrão que deriva de IPageRouteModelProvider invoca
convenções que são projetadas para fornecer pontos de extensibilidade para configurar
rotas de página.

Convenção de modelo de rota de pasta


Use AddFolderRouteModelConvention para criar e adicionar um
IPageRouteModelConvention que invoca uma ação no PageRouteModel para todas as
páginas na pasta especificada.

O aplicativo de exemplo usa AddFolderRouteModelConvention para adicionar um


modelo de rota {otherPagesTemplate?} às páginas da pasta OtherPages:

C#

options.Conventions.AddFolderRouteModelConvention("/OtherPages", model =>


{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{otherPagesTemplate?}"),
}
});
}
});

A propriedade Order do AttributeRouteModel é definida como 2 . Isso garante que o


modelo para {globalTemplate?} (definido anteriormente no tópico como 1 ) tenha
prioridade para a primeira posição de valor de dados de rota quando um único valor de
rota for fornecido. Se uma página na pasta Pages/OtherPages for solicitada com um
valor de parâmetro de rota (por exemplo, /OtherPages/Page1/RouteDataValue ),
"RouteDataValue" será carregado em RouteData.Values["globalTemplate"] ( Order = 1 )
e não RouteData.Values["otherPagesTemplate"] ( Order = 2 ) devido à definição da Order
propriedade.

Sempre que possível, não defina o , o Order que resulta em Order = 0 . Conte com o
roteamento para selecionar a rota correta.

Solicite a página Page1 da amostra em


localhost:5000/OtherPages/Page1/GlobalRouteValue/OtherPagesRouteValue e inspecione

o resultado:

Convenção de modelo de rota de página


Use AddPageRouteModelConvention para criar e adicionar um
IPageRouteModelConvention que invoca uma ação no PageRouteModel para a página
com o nome especificado.
O aplicativo de exemplo usa AddPageRouteModelConvention para adicionar um modelo de
rota {aboutTemplate?} à página About:

C#

options.Conventions.AddPageRouteModelConvention("/About", model =>


{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{aboutTemplate?}"),
}
});
}
});

A propriedade Order do AttributeRouteModel é definida como 2 . Isso garante que o


modelo para {globalTemplate?} (definido anteriormente no tópico como 1 ) tenha
prioridade para a primeira posição de valor de dados de rota quando um único valor de
rota for fornecido. Se a página Sobre for solicitada com um valor de parâmetro de rota
em /About/RouteDataValue , "RouteDataValue" será carregado em
RouteData.Values["globalTemplate"] ( Order = 1 ) e não
RouteData.Values["aboutTemplate"] ( Order = 2 ) devido à configuração da Order

propriedade.

Sempre que possível, não defina o , que Order resulta em Order = 0 . Conte com o
roteamento para selecionar a rota correta.

Solicite a página About da amostra em localhost:


{port}/About/GlobalRouteValue/AboutRouteValue e inspecione o resultado:
A saída do agente é exibida:

CLI do .NET

info: SampleApp.Pages.AboutModel[0]
/About/GlobalRouteValue/AboutRouteValue Order = 2 Template =
About/{globalTemplate?}/{aboutTemplate?}

Usar um transformador de parâmetro para


personalizar rotas de página
Consulte Transformadores de parâmetro.

Configurar uma rota de página


Use AddPageRoute para configurar uma rota para uma página no caminho de página
especificado. Os links gerados para a página usam a rota especificada. AddPageRoute
usa AddPageRouteModelConvention para estabelecer a rota.

O aplicativo de exemplo cria uma rota para /TheContactPage para a Contact Razor
Página:

C#

options.Conventions.AddPageRoute("/Contact", "TheContactPage/{text?}");

A Contact página também pode ser acessada em / Contact1' por meio de sua rota
padrão.
A rota personalizada do aplicativo de exemplo para a Contact página permite um
segmento de rota opcional text ( {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 :

CSHTML

@page "{text?}"
@model ContactModel
@{
ViewData["Title"] = "Contact";
}

<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>

<address>
One Microsoft Way<br>
Redmond, WA 98052-6399<br>
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong> <a
href="mailto:Support@example.com">Support@example.com</a><br>
<strong>Marketing:</strong> <a
href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>

<p>@Model.RouteDataTextTemplateValue</p>

Observe que a URL gerada para o link Contato na página renderizada reflete a rota
atualizada:

Visite a Contact página 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 invoca convenções
projetadas para fornecer pontos de extensibilidade para configurar modelos
IPageApplicationModelProvider 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 nesta seção, o aplicativo de exemplo usa uma AddHeaderAttribute


classe , que é uma ResultFilterAttribute, que aplica um cabeçalho de resposta:

C#

public class AddHeaderAttribute : ResultFilterAttribute


{
private readonly string _name;
private readonly string[] _values;

public AddHeaderAttribute(string name, string[] values)


{
_name = name;
_values = values;
}

public override void OnResultExecuting(ResultExecutingContext context)


{
context.HttpContext.Response.Headers.Add(_name, _values);
base.OnResultExecuting(context);
}
}

Usando convenções, a amostra explica como aplicar o atributo a todas as páginas de


uma pasta e a uma única página.

Convenção de modelo de aplicativo de pasta

Use AddFolderApplicationModelConvention para criar e adicionar um


IPageApplicationModelConvention que invoca uma ação em PageApplicationModel
instâncias para 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:

C#

options.Conventions.AddFolderApplicationModelConvention("/OtherPages", model
=>
{
model.Filters.Add(new AddHeaderAttribute(
"OtherPagesHeader", new string[] { "OtherPages Header Value" }));
});

Solicite a página Page1 da amostra em localhost:5000/OtherPages/Page1 e inspecione


os cabeçalhos para exibir o resultado:

Convenção de modelo de aplicativo de página

Use AddPageApplicationModelConvention para criar e adicionar um


IPageApplicationModelConvention que invoca uma ação no PageApplicationModel para
a página com o nome especificado.

A amostra explica o uso de AddPageApplicationModelConvention adicionando um


cabeçalho, AboutHeader , à página About:
C#

options.Conventions.AddPageApplicationModelConvention("/About", model =>


{
model.Filters.Add(new AddHeaderAttribute(
"AboutHeader", new string[] { "About Header Value" }));
});

Solicite a página About da amostra em localhost:5000/About e inspecione os


cabeçalhos para exibir o resultado:

Configurar um filtro

ConfigureFilter configura o filtro especificado a ser aplicado. É possível implementar


uma classe de filtro, mas o aplicativo de exemplo mostra como implementar um filtro
em uma expressão lambda, que é implementado em segundo plano como um alocador
que retorna um filtro:

C#

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

Páginas, o EmptyFilter não terá efeito conforme o esperado se o caminho não contiver
OtherPages/Page2 .

Solicite a página Page2 da amostra em localhost:5000/OtherPages/Page2 e inspecione


os cabeçalhos para exibir o resultado:

Configurar um alocador de filtro

ConfigureFilter configura a fábrica especificada para aplicar filtros a todas as Razor


Páginas.

O aplicativo de exemplo fornece um exemplo de como usar um alocador de filtro


adicionando um cabeçalho, FilterFactoryHeader , com dois valores para as páginas do
aplicativo:

C#

options.Conventions.ConfigureFilter(new AddHeaderWithFactory());

AddHeaderWithFactory.cs :

C#

public class AddHeaderWithFactory : IFilterFactory


{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new AddHeaderFilter();
}

private class AddHeaderFilter : IResultFilter


{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"FilterFactoryHeader",
new string[]
{
"Filter Factory Header Value 1",
"Filter Factory Header Value 2"
});
}

public void OnResultExecuted(ResultExecutedContext context)


{
}
}

public bool IsReusable


{
get
{
return false;
}
}
}

Solicite a página About da amostra em localhost:5000/About e inspecione os


cabeçalhos para exibir o resultado:

Filtros MVC e o filtro de Página (IPageFilter)


Os filtros de Ação do MVC são ignorados por Razor Páginas, pois Razor as Páginas usam
métodos de manipulador. Outros tipos de filtros MVC estão disponíveis para uso:
Autorização, Exceção, Recurso e Resultado. Para obter mais informações, consulte o
tópico Filtros.

O Filtro de página (IPageFilter) é um filtro que se aplica a Razor Páginas. Para obter mais
informações, consulte Filtrar métodos para Razor Páginas.
Recursos adicionais
Razor Roteamento de Páginas
Convenções de autorização do Razor Pages no ASP.NET Core
Áreas no ASP.NET Core
Visão geral sobre o ASP.NET Core MVC
Artigo • 15/01/2023 • 10 minutos para o fim da leitura

Por Steve Smith

O ASP.NET Core MVC é uma estrutura avançada para a criação de aplicativos Web e
APIs usando o padrão de design Model-View-Controller.

Padrão MVC
O padrão de arquitetura MVC (Model-View-Controller) separa um aplicativo em três
grupos de componentes principais: Modelos, Exibições e Componentes. Esse padrão
ajuda a obter a separação de interesses. Usando esse padrão, as solicitações de usuário
são encaminhadas para um Controlador, que é responsável por trabalhar com o Modelo
para executar as ações do usuário e/ou recuperar os resultados de consultas. O
Controlador escolhe a Exibição a ser exibida para o usuário e fornece-a com os dados
do Modelo solicitados.

O seguinte diagrama mostra os três componentes principais e quais deles referenciam


os outros:

Essa descrição das responsabilidades ajuda você a dimensionar o aplicativo em termos


de complexidade, porque é mais fácil de codificar, depurar e testar algo (modelo,
exibição ou controlador) que tem um único trabalho. É 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.

7 Observação

A exibição e o controlador dependem do modelo. No entanto, o modelo não


depende da exibição nem do controlador. Esse é um dos principais benefícios da
separação. Essa separação permite que o modelo seja criado e testado de forma
independente da apresentação visual.

Responsabilidades do Modelo
O Modelo em um aplicativo MVC representa o estado do aplicativo e qualquer lógica de
negócios ou operação que deve ser executada por ele. A lógica de negócios deve ser
encapsulada no modelo, juntamente com qualquer lógica de implementação, para
persistir o estado do aplicativo. As exibições fortemente tipadas normalmente usam
tipos ViewModel criados para conter os dados a serem exibidos nessa exibição. O
controlador cria e popula essas instâncias de ViewModel com base no modelo.

Responsabilidades da Exibição
As exibições são responsáveis por apresentar o conteúdo por meio da interface do
usuário. Eles usam o mecanismo de exibiçãoRazor para inserir o código .NET na
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).
7 Observação

Os controladores não devem ser excessivamente complicados por muitas


responsabilidades. Para evitar que a lógica do controlador se torne excessivamente
complexa, efetue push da lógica de negócios para fora do controlador e insira-a no
modelo de domínio.

 Dica

Se você achar que as ações do controlador executam com frequência os mesmos


tipos de ações, mova essas ações comuns para filtros.

ASP.NET Core MVC


A estrutura do ASP.NET Core MVC é uma estrutura de apresentação leve, de software
livre e altamente testável, otimizada para uso com o ASP.NET Core.

ASP.NET Core MVC fornece uma maneira com base em padrões para criar sites
dinâmicos que habilitam uma separação limpa de preocupações. Ele lhe dá controle
total sobre a marcação, dá suporte ao desenvolvimento amigável a TDD e usa os
padrões da web mais recentes.

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 que você defina globalmente os formatos


de URL que seu aplicativo aceita e como cada um desses formatos é mapeado para um
método de ação específico em um 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.
C#

routes.MapRoute(name: "Default", template: "


{controller=Home}/{action=Index}/{id?}");

O roteamento de atributo permite que você especifique as informações de roteamento


decorando os controladores e as ações com atributos que definem as rotas do
aplicativo. Isso significa que as definições de rota são colocadas ao lado do controlador
e da ação aos quais estão associadas.

C#

[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
...
}
}

Model binding
ASP.NET Core associação de modelo MVC converte dados de solicitação do cliente
(valores de formulário, dados de rota, parâmetros de cadeia de consulta, 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.

C#

public async Task<IActionResult> Login(LoginViewModel model, string


returnUrl = null) { ... }

Validação de modelos
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.

C#
using System.ComponentModel.DataAnnotations;
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }

[Required]
[DataType(DataType.Password)]
public string Password { get; set; }

[Display(Name = "Remember me?")]


public bool RememberMe { get; set; }
}

Uma ação do controlador:

C#

public async Task<IActionResult> Login(LoginViewModel model, string


returnUrl = null)
{
if (ModelState.IsValid)
{
// work with the model
}
// At this point, something failed, redisplay form
return View(model);
}

A estrutura manipula a validação dos dados de solicitação no cliente e no servidor. A


lógica de validação especificada em tipos de modelo é adicionada às exibições
renderizados como anotações não invasivas e é imposta no navegador com o jQuery
Validation .

Injeção de dependência
O ASP.NET Core tem suporte interno para DI (injeção de dependência). No ASP.NET
Core MVC, os controladores podem solicitar serviços necessários por meio de seus
construtores, possibilitando o acompanhamento do princípio de dependências
explícitas.

O aplicativo também pode usar a injeção de dependência em arquivos no exibição,


usando a diretiva @inject :

CSHTML
@inject SomeService ServiceName

<!DOCTYPE html>
<html lang="en">
<head>
<title>@ServiceName.GetTitle</title>
</head>
<body>
<h1>@ServiceName.GetTitle</h1>
</body>
</html>

Filtros
Os filtros ajudam os desenvolvedores a encapsular interesses paralelos, como
tratamento de exceção ou autorização. Os filtros permitem a execução de uma lógica
pré e pós-processamento personalizada para métodos de ação e podem ser
configurados para execução em determinados pontos no pipeline de execução de uma
solicitação específica. Os filtros podem ser aplicados a controladores ou ações como
atributos (ou podem ser executados globalmente). Vários filtros (como Authorize ) são
incluídos na estrutura. [Authorize] é o atributo usado para criar filtros de autorização
do MVC.

C#

[Authorize]
public class AccountController : Controller

Áreas
As áreas fornecem uma maneira de particionar um aplicativo Web MVC ASP.NET Core
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 tem suas próprias
exibições de componentes lógicos, 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 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.

Razor mecanismo de exibição


ASP.NET Core modos de exibição MVC usam o Razor mecanismo de exibição para
renderizar exibições. Razor é uma linguagem de marcação de modelo compacta,
expressiva e fluida para definir exibições usando código C# inserido. Razor é usado para
gerar conteúdo da Web dinamicamente no servidor. Você pode combinar o código do
servidor com o código e o conteúdo do lado cliente de maneira limpa.

CSHTML

<ul>
@for (int i = 0; i < 5; i++) {
<li>List item @i</li>
}
</ul>

Usando o Razor mecanismo de exibição, você pode definir layouts, exibições parciais e
seções substituíveis.

Exibições fortemente tipadas


Razor as exibições no MVC podem ser fortemente tipadas com base em seu 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> :

CSHTML

@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 lado do servidor participe na criação
e renderização de elementos HTML em Razor arquivos. 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 :

CSHTML

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

CSHTML
<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.js"
asp-fallback-src="~/lib/jquery/dist/jquery.js"
asp-fallback-test="window.jQuery">
</script>
</environment>

Os Auxiliares de Marca fornecem uma experiência de desenvolvimento amigável com


HTML e um ambiente intelliSense avançado para criar HTML e Razor marcação. A
maioria dos Auxiliares de Marca internos é direcionada a elementos HTML existentes e
fornece atributos do lado do servidor para o elemento.

Componentes da exibição
Os Componentes de Exibição permitem que você empacote a lógica de renderização e
reutilize-a em todo o aplicativo. São semelhantes às exibições parciais, mas com a lógica
associada.

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 a versão de compatibilidade para ASP.NET Core
MVC.

Recursos adicionais
MyTested.AspNetCore.Mvc – Biblioteca de Testes Fluentes para ASP.NET Core
MVC : biblioteca de testes de unidade fortemente tipada, fornecendo uma
interface fluente para testar aplicativos MVC e API Web. (Não mantido ou com
suporte da Microsoft.)
Pré-gerar e integrar componentes ASP.NET Core Razor
Injeção de dependência no ASP.NET Core
Introdução ao ASP.NET Core MVC
Artigo • 15/11/2022 • 21 minutos para o fim da leitura

De Rick Anderson

Este tutorial ensina a usar o desenvolvimento Web do ASP.NET Core MVC com
controladores e exibições. Se você não estiver familiarizado com ASP.NET Core
desenvolvimento na Web, considere a Razor versão páginas deste tutorial, que fornece
um ponto de partida mais fácil. Consulte Escolher um ASP.NET Core interface do
usuário, que compara Razor Pages, MVC e Blazor para desenvolvimento de interface do
usuário.

Este é o primeiro tutorial de uma série que ensina ASP.NET Core desenvolvimento da
Web do MVC com controladores e exibições.

No final da série, você terá um aplicativo que gerencia e exibe dados de filme. Você
aprenderá como:

" Crie um aplicativo Web.


" Adicionar e gerar o scaffolding de um modelo.
" Trabalhar com um banco de dados.
" Adicionar pesquisa e validação.

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

Pré-requisitos
Visual Studio

Versão prévia mais recente do Visual Studio 2022 com a carga de trabalho
de ASP.NET e desenvolvimento web .

Criar um aplicativo Web


Visual Studio

Inicie o Visual Studio e selecione Criar um projeto.


Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core
Aplicativo Web (Model-View-Controller)>Avançar.
Na caixa de diálogo Configurar seu novo projeto , insira MvcMovie para Nome
do projeto. É importante nomear o projeto MvcMovie. A capitalização precisa
corresponder a cada namespace um quando o código é copiado.
Selecione Avançar.
Na caixa de diálogo Informações adicionais:
Selecione .NET 7.0.
Verifique se Não usar instruções de nível superior está desmarcado .
Selecione Criar.

Para obter mais informações, incluindo abordagens alternativas para criar o projeto,
consulte Criar um novo projeto no Visual Studio.

O Visual Studio usa o modelo de projeto padrão para o projeto MVC criado. O
projeto criado:

É um aplicativo funcional.
É um projeto inicial básico.

Executar o aplicativo

Visual Studio
Selecione Ctrl+F5 para executar o aplicativo sem o depurador.

O Visual Studio exibe a seguinte caixa de diálogo quando um projeto ainda


não está configurado para usar o SSL:

Selecione Sim se você confia no certificado SSL do IIS Express.

A seguinte caixa de diálogo é exibida:

Selecione Sim se você concordar com confiar no certificado de


desenvolvimento.

Para obter informações sobre como confiar no navegador Firefox, consulte


Firefox SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

O Visual Studio executa o aplicativo e abre o navegador padrão.


A barra de endereços mostra localhost:<port#> e não algo como example.com . O
nome do host padrão para seu computador local é localhost . Uma porta aleatória
é usada para o servidor Web quando o Visual Studio cria um projeto Web.

Iniciar o aplicativo sem depuração selecionando Ctrl+F5 permite:

Realize alterações de código.


Salve o arquivo.
Atualize rapidamente o navegador e veja as alterações de código.

Você pode iniciar o aplicativo no modo de depuração ou não depuração no menu


Depurar :

Você pode depurar o aplicativo selecionando o botão https na barra de


ferramentas :

A imagem a seguir mostra o aplicativo:


Visual Studio

Ajuda do Visual Studio


Saiba como depurar código C# usando o Visual Studio
Introdução ao IDE do Visual Studio

No próximo tutorial desta série, você aprenderá sobre o MVC e começará a escrever
algum código.

Próximo: Adicionar um controlador


Parte 2, adicione um controlador a um
aplicativo MVC ASP.NET Core
Artigo • 02/12/2022 • 19 minutos para o fim da leitura

De Rick Anderson

O padrão de arquitetura MVC (Model-View-Controller) separa um aplicativo em três


componentes principais: Model, View e Controller. O padrão MVC ajuda a criar
aplicativos que são mais testáveis e fáceis de atualizar comparado aos aplicativos
monolíticos tradicionais.

Os aplicativos baseados no MVC contêm:

Models: classes que representam os dados do aplicativo. As classes de modelo


usam a lógica de validação para impor regras de negócio aos dados.
Normalmente, os objetos de modelo recuperam e armazenam o estado do
modelo em um banco de dados. Neste tutorial, um modelo Movie recupera dados
de filmes de um banco de dados, fornece-os para a exibição ou atualiza-os. O
dados atualizados são gravados em um banco de dados.
Views: exibições são os componentes que exibem a interface do usuário do
aplicativo. Em geral, essa interface do usuário exibe os dados de modelo.
Controladores C: classes que:
Lidar com solicitações de navegador.
Recuperar dados do modelo.
Modelos de exibição de chamada que retornam uma resposta.

Em um aplicativo MVC, a exibição exibe apenas informações. O controlador manipula e


responde à entrada e interação do usuário. Por exemplo, o controlador manipula
segmentos de URL e valores de cadeia de caracteres de consulta e passa esses valores
para o modelo. O modelo pode usar esses valores para consultar o banco de dados. Por
exemplo:

https://localhost:5001/Home/Privacy : especifica o Home controlador e a ação

Privacy .

https://localhost:5001/Movies/Edit/5 : é uma solicitação para editar o filme com


ID=5 usando o Movies controlador e a ação Edit , que são detalhados
posteriormente no tutorial.

Os dados de rota são explicados posteriormente no tutorial.


O padrão de arquitetura MVC separa um aplicativo em três grupos principais de
componentes: Modelos, Exibições e Controladores. Esse padrão ajuda a alcançar a
separação de preocupações: a lógica da interface do usuário pertence à exibição. A
lógica de entrada pertence ao controlador. A lógica de negócios pertence ao modelo.
Essa separação ajuda a gerenciar a complexidade ao criar um aplicativo, pois permite
trabalhar em um aspecto da implementação de cada vez sem afetar o código de outro.
Por exemplo, você pode trabalhar no código de exibição sem depender do código da
lógica de negócios.

Esses conceitos são introduzidos e demonstrados nesta série de tutoriais ao criar um


aplicativo de filme. O projeto MVC contém pastas para os Controladores e as Exibições.

Adicionar um controlador
Visual Studio

Em Gerenciador de Soluções, clique com o botão direito do mouse em


Controladores > Adicionar > Controlador.
Na caixa de diálogo Adicionar Novo Item Scaffolded, selecione Controlador MVC
– Adicionar Vazio>.

Na caixa de diálogo Adicionar Novo Item – MvcMovie , insira


HelloWorldController.cs e selecione Adicionar.

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

C#

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

namespace MvcMovie.Controllers;

public class HelloWorldController : Controller


{
//
// GET: /HelloWorld/
public string Index()
{
return "This is my default action...";
}
//
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}

Cada método public em um controlador pode ser chamado como um ponto de


extremidade HTTP. Na amostra acima, ambos os métodos retornam uma cadeia de
caracteres. Observe os comentários que precedem cada método.

Um ponto de extremidade HTTP:

É uma URL de destino no aplicativo Web, como


https://localhost:5001/HelloWorld .

Combina:
O protocolo usado: HTTPS .
O local de rede do servidor Web, incluindo a porta TCP: localhost:5001 .
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. Posteriormente no tutorial, o
mecanismo de scaffolding é usado para gerar HTTP POST métodos, que atualizam os
dados.

Execute o aplicativo sem o depurador.

Acrescente "HelloWorld" ao caminho na barra de endereços. O método Index retorna


uma cadeia de caracteres.

O MVC invoca classes de 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 qual código invocar:
/[Controller]/[ActionName]/[Parameters]

O formato de roteamento é definido no Program.cs arquivo .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Quando você navega até o aplicativo e não fornece segmentos de URL, ele usa como
padrão o controlador "Home" e o método "Index" especificado na linha de modelo
realçada acima. Nos segmentos de URL anteriores:

O primeiro segmento de URL determina a classe do controlador a ser executada.


Portanto, localhost:5001/HelloWorld mapeia para a classe Controlador
HelloWorld .
A segunda parte do segmento de URL determina o método de ação na classe.
Portanto, localhost:5001/HelloWorld/Index faz com que o Index método da
HelloWorldController classe seja executado. Observe que você precisou apenas
navegar para localhost:5001/HelloWorld e o método Index foi chamado por
padrão. 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 até: https://localhost:{PORT}/HelloWorld/Welcome . Substitua {PORT} pelo


número da porta.

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.

C#

// 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 para proteger o aplicativo contra entradas HtmlEncoder.Default.Encode mal-


intencionadas, como por meio do JavaScript.
Usa Cadeias de caracteres interpoladas em $"Hello {name}, NumTimes is:
{numTimes}" .

Execute o aplicativo e navegue até: https://localhost:{PORT}/HelloWorld/Welcome?


name=Rick&numtimes=4 . Substitua {PORT} pelo número da porta.

Experimente valores diferentes para name e numtimes na URL. O sistema de associação


de modelo MVC mapeia automaticamente os parâmetros nomeados da cadeia de
caracteres de consulta para os parâmetros no método . Consulte Model binding para
obter mais informações.

Na imagem anterior:

O segmento Parameters de URL não é usado.


Os name parâmetros e numTimes são passados na cadeia de caracteres de
consulta .
O ? (ponto de interrogação) na URL acima é um separador e a cadeia de
caracteres de consulta segue.
O & caractere separa pares campo-valor.

Substitua o método Welcome pelo seguinte código:

C#

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


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

Execute o aplicativo e insira a seguinte URL: https://localhost:


{PORT}/HelloWorld/Welcome/3?name=Rick

Na URL anterior:

O terceiro segmento de URL correspondeu ao parâmetro id de rota .


O método Welcome contém um parâmetro id que correspondeu ao modelo de
URL no método MapControllerRoute .
O à direita inicia a cadeia de ? caracteres de consulta .

C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

No exemplo anterior:

O terceiro segmento de URL correspondeu ao parâmetro id de rota .


O método Welcome contém um parâmetro id que correspondeu ao modelo de
URL no método MapControllerRoute .
O ? à direita (em id? ) indica que o parâmetro id é opcional.

Anterior: Introdução ao Próximo: Adicionar um Modo de Exibição


Parte 3, adicionar uma exibição a um
aplicativo MVC ASP.NET Core
Artigo • 02/12/2022 • 25 minutos para o fim da leitura

De Rick Anderson

Nesta seção, você modificará a HelloWorldController classe para usar arquivos de


exibição Razor . Isso encapsula corretamente o processo de geração de respostas HTML
para um cliente.

Os modelos de exibição são criados usando Razor. RazorModelos de exibição baseados


em:

Ter uma .cshtml extensão de arquivo.


Forneça uma maneira elegante de criar uma saída HTML com C#.

Atualmente, o Index método retorna uma cadeia de caracteres com uma mensagem na
classe de controlador. Na classe HelloWorldController , substitua o método Index pelo
seguinte código:

C#

public IActionResult Index()


{
return View();
}

O código anterior:

Chama o método do View controlador.


Usa um modelo de exibição para gerar uma resposta HTML.

Métodos do controlador:

São chamados de métodos de ação. Por exemplo, o método de Index ação no


código anterior.
Geralmente, retorna um IActionResult ou uma classe derivada de ActionResult, não
um tipo como string .

Adicionar uma exibição


Visual Studio

Clique com o botão direito do mouse na pasta Exibições e em Adicionar > Nova
Pasta e nomeie a pasta HelloWorld.

Clique com o botão direito do mouse na pasta Views/HelloWorld e, em seguida, em


Adicionar > Novo Item.

Na caixa de diálogo Adicionar Novo Item – MvcMovie :

Na caixa de pesquisa no canto superior direito, insira exibição


Selecionar Razor Exibição – Vazio
Mantenha o valor da caixa Nome , Index.cshtml .
Selecione Adicionar

Substitua o conteúdo do arquivo de Views/HelloWorld/Index.cshtml Razor exibição pelo


seguinte:

CSHTML

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

<h2>Index</h2>

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


Acesse o diretório https://localhost:{PORT}/HelloWorld :

O Index método na HelloWorldController executou a instrução return View(); ,


que especificou que o método deve usar um arquivo de modelo de exibição para
renderizar uma resposta ao navegador.

Um nome de arquivo de modelo de exibição não foi especificado, portanto, o MVC


usa o arquivo de exibição padrão como padrão. Quando o nome do arquivo de
exibição não é especificado, o modo de exibição padrão é retornado. A exibição
padrão tem o mesmo nome que o método de ação, Index neste exemplo. O
modelo de exibição /Views/HelloWorld/Index.cshtml é usado.

A imagem a seguir mostra a cadeia de caracteres "Olá do nosso Modelo de


Exibição!" embutida em código no modo de exibição:

Alterar exibições e páginas de layout


Selecione os links de menu MvcMovie, Homee Privacy. Cada página mostra o mesmo
layout de menu. O layout do menu é implementado no Views/Shared/_Layout.cshtml
arquivo .

Abra o arquivo Views/Shared/_Layout.cshtml .

Os modelos de layout permitem:

Especificando o layout de contêiner HTML de um site em um só lugar.


Aplicando o layout do contêiner HTML em várias páginas no site.
Localize a linha @RenderBody() . RenderBody é um espaço reservado em que todas as
páginas específicas à exibição criadas são mostradas, encapsuladas na página de layout.
Por exemplo, se você selecionar o Privacy link, a exibição Views/Home/Privacy.cshtml
será renderizada dentro do RenderBody método .

Alterar o título, o rodapé e o link de menu no


arquivo de layout
Substitua o conteúdo do Views/Shared/_Layout.cshtml arquivo pela marcação a seguir.
As alterações são realçadas:

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie App</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
</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-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Movies"
asp-action="Index">Movie App</a>
<button class="navbar-toggler" type="button" data-bs-
toggle="collapse" data-bs-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
justify-content-between">
<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>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2022 - Movie App - <a asp-area="" asp-controller="Home"
asp-action="Privacy">Privacy</a>
</div>
</footer>
<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>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

A marcação anterior fez as seguintes alterações:

Três ocorrências de MvcMovie para Movie App .


O elemento de âncora <a class="navbar-brand" asp-area="" asp-
controller="Home" asp-action="Index">MvcMovie</a> para <a class="navbar-brand"

asp-controller="Movies" asp-action="Index">Movie App</a> .

Na marcação anterior, o atributo auxiliar de marca de âncora e o asp-area="" valor do


atributo foram omitidos porque este aplicativo não está usando Áreas.

Observação: o Movies controlador não foi implementado. Neste ponto, o Movie App
link não é funcional.

Salve as alterações e selecione o Privacy link. Observe como o título na guia do


navegador exibe Privacy Política – Aplicativo de Filme em vez de Privacy Política –
MvcMovie
Selecione o link Home.

Observe que o título e o texto de âncora exibem o Aplicativo de Filme. As alterações


foram feitas uma vez no modelo de layout e todas as páginas no site refletem o novo
texto do link e o novo título.

Examine o Views/_ViewStart.cshtml arquivo:

CSHTML

@{
Layout = "_Layout";
}

O Views/_ViewStart.cshtml arquivo traz o Views/Shared/_Layout.cshtml arquivo para


cada exibição. A propriedade Layout pode ser usada para definir outra exibição de
layout ou defina-a como null para que nenhum arquivo de layout seja usado.

Abra o Views/HelloWorld/Index.cshtml arquivo de exibição.

Altere o título e <h2> o elemento conforme realçado no seguinte:

CSHTML

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

<h2>My Movie List</h2>

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

O título e <h2> o elemento são ligeiramente diferentes, portanto, fica claro 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:

CSHTML

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

Salve as alterações e navegue para https://localhost:{PORT}/HelloWorld .

Observe que os seguintes foram alterados:

Título do navegador.
Título primário.
Títulos secundários.

Se não houver alterações no navegador, poderá ser armazenado em cache o conteúdo


que está sendo exibido. Pressione Ctrl+F5 no navegador para forçar a resposta do
servidor a ser carregada. O título do navegador é criado com ViewData["Title"] o que
definimos no Index.cshtml modelo de exibição e o "- Aplicativo de Filme" adicional
adicionado no arquivo de layout.

O conteúdo no modelo de exibição Index.cshtml é mesclado com o modelo de


exibição Views/Shared/_Layout.cshtml . Uma única resposta HTML é enviada ao
navegador. Os modelos de layout facilitam a realização de alterações que se aplicam a
todas as páginas de um aplicativo. Para saber mais, confira Layout.

No entanto, o pequeno bit de "dados", a mensagem "Olá do nosso Modelo de


Exibição!", é embutido em código. O aplicativo MVC tem um "V" (exibição), um "C"
(controlador), mas nenhum "M" (modelo) ainda.
Passando dados do controlador para a exibição
As ações do controlador são invocadas em resposta a uma solicitação de URL de
entrada. Uma classe de controlador é o local em que o código é escrito e que manipula
as solicitações recebidas do navegador. O controlador recupera dados de uma fonte de
dados e decide qual tipo de resposta será enviada novamente para o navegador.
Modelos de exibição podem ser usados em um controlador para gerar e formatar uma
resposta HTML para o navegador.

Os controladores são responsáveis por fornecer os dados necessários para que um


modelo de exibição renderize uma resposta.

Os modelos de exibição não devem:

Fazer lógica de negócios


Interagir diretamente com um banco de dados.

Um modelo de exibição deve funcionar apenas com os dados fornecidos a ele pelo
controlador. Manter essa "separação de preocupações" ajuda a manter o código:

Limpo.
Testável.
Sustentável.

Atualmente, o Welcome método na classe usa um name parâmetro e ID e, em


HelloWorldController seguida, gera os valores diretamente para o navegador.

Em vez de fazer com que o controlador renderize a resposta como uma cadeia de
caracteres, altere o controlador para que ele use um modelo de exibição. O modelo de
exibição gera uma resposta dinâmica, o que significa que os dados apropriados devem
ser passados do controlador para a exibição para gerar a resposta. Faça isso fazendo
com que o controlador coloque os dados dinâmicos (parâmetros) de que o modelo de
exibição precisa em um ViewData dicionário. O modelo de exibição pode acessar os
dados dinâmicos.

Em HelloWorldController.cs , altere o Welcome método para adicionar um Message valor


e NumTimes ao ViewData dicionário.

O ViewData dicionário é um objeto dinâmico, o que significa que qualquer tipo pode ser
usado. O ViewData objeto não tem propriedades definidas até que algo seja adicionado.
O sistema de associação de modelo MVC mapeia automaticamente os parâmetros
nomeados e numTimes da cadeia de caracteres name de consulta para os parâmetros no
método . O completo HelloWorldController :
C#

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

namespace MvcMovie.Controllers;

public class HelloWorldController : Controller


{
public IActionResult Index()
{
return View();
}
public IActionResult Welcome(string name, int numTimes = 1)
{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;
return View();
}
}

O objeto de dicionário ViewData contém dados que serão passados para a exibição.

Crie um modelo de exibição de 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:

CSHTML

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

<h2>Welcome</h2>

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

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

https://localhost:{PORT}/HelloWorld/Welcome?name=Rick&numtimes=4
Os dados são obtidos da URL e passados para o controlador usando o associador de
modelo MVC. O controlador empacota os dados em um dicionário ViewData e passa
esse objeto para a exibição. Em seguida, a exibição renderiza os dados como HTML para
o navegador.

No exemplo anterior, o ViewData dicionário 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 do modelo de
exibição para passar dados é preferencial em relação à abordagem de ViewData
dicionário.

No próximo tutorial, será criado um banco de dados de filmes.

Anterior: Adicionar um controlador em seguida: Adicionar um modelo


Parte 4, adicionar um modelo a um
aplicativo MVC ASP.NET Core
Artigo • 06/12/2022 • 60 minutos para o fim da leitura

Por Rick Anderson e Jon P Smith .

Neste tutorial, classes são adicionadas para gerenciar filmes em um banco de dados.
Essas classes são a parte "Model" do aplicativo de VC M.

Essas classes de modelo são usadas com o Entity Framework Core (EF Core) para
trabalhar com um banco de dados. EF Core é uma estrutura orm (mapeamento
relacional de objeto) que simplifica o código de acesso a dados que você precisa
escrever.

As classes de modelo criadas são conhecidas como classes POCO , de Plain Old CLR
Objects. As classes POCO não têm nenhuma dependência em EF Core. Eles definem
apenas as propriedades dos dados a serem armazenados no banco de dados.

Neste tutorial, as classes de modelo são criadas primeiro e EF Core criam o banco de
dados.

Adicionar uma classe de modelo de dados


Visual Studio

Clique com o botão direito do mouse na pasta >ModelosAdicionar>Classe. Atribua


um nome ao arquivo Movie.cs .

Atualize o Models/Movie.cs arquivo com o seguinte código:

C#

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

A classe Movie contém um campo Id , que é exigido pelo banco de dados para a chave
primária.

O DataType atributo em ReleaseDate especifica o tipo dos dados ( Date ). Com esse
atributo:

O usuário não precisa inserir informações de hora no campo de data.


Somente a data é exibida, não as informações de tempo.

DataAnnotations são abordados em um tutorial posterior.

O ponto de interrogação após string indica que a propriedade é anulável. Para obter
mais informações, confira Tipos de referência anuláveis.

Adicionar pacotes NuGet


Visual Studio

No menu Ferramentas, selecione Gerenciador de Pacotes NuGet>Console do


Gerenciador de Pacotes (PMC).

No PMC, execute os seguintes comandos:

PowerShell
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.SqlServer

Os comandos anteriores adicionam:

O EF Core provedor de SQL Server. O pacote do provedor instala o EF Core


pacote como uma dependência.
Os utilitários usados pelos pacotes instalados automaticamente na etapa de
scaffolding, posteriormente no tutorial.

Compile o projeto como uma verificação de erros do compilador.

Aplicar scaffold a páginas de filme


Use a ferramenta de scaffolding para produzir Create páginas CRUD (, Read Update , e
Delete ) para o modelo de filme.

Visual Studio

Em Gerenciador de Soluções, clique com o botão direito do mouse na pasta


Controladores e selecione Adicionar > Novo Item Com Scaffolded.

Na caixa de diálogo Adicionar Novo Item Scaffolded , selecione Controlador MVC


com exibições, usando Entity Framework > Add.
Conclua a caixa de diálogo Adicionar Controlador MVC com exibições usando o
Entity Framework :

Na lista suspensa Classe de modelo, selecione Filme (MvcMovie.Models).


Na linha Classe de contexto de dados, selecione o sinal de + (adição).
Na caixa de diálogo Adicionar Contexto de Dados , o nome da classe
MvcMovie.Data.MvcMovieContext é gerado.
Selecione Adicionar.
Exibições e Nome do controlador: mantenha o padrão.
Selecione Adicionar.
Se você receber uma mensagem de erro, selecione Adicionar uma segunda vez
para tentar novamente.

O scaffolding atualiza o seguinte:

Insere as referências de pacote necessárias no arquivo de MvcMovie.csproj


projeto.
Registra o contexto do banco de dados no Program.cs arquivo .
Adiciona uma cadeia de conexão de banco de dados ao appsettings.json
arquivo.

O scaffolding cria o seguinte:

Um controlador de filmes: Controllers/MoviesController.cs


Razor exibir arquivos para páginas Criar, Excluir, Detalhes, Editar e Índice :
Views/Movies/*.cshtml

Uma classe de contexto de banco de dados: Data/MvcMovieContext.cs

A criação automática desses arquivos e atualizações de arquivo é conhecida como


scaffolding.

As páginas com scaffolded ainda não podem ser usadas porque o banco de dados não
existe. Executar o aplicativo e selecionar o link aplicativo de filme resulta em um Não é
possível abrir o banco de dados ou nenhuma tabela desse tipo: Mensagem de erro de
filme.
Compile o aplicativo para verificar se não há erros.

Migração inicial
Use o EF Core recurso Migrações para criar o banco de dados. As migrações são um
conjunto de ferramentas que criam e atualizam um banco de dados para corresponder
ao modelo de dados.

Visual Studio

No menu Ferramentas, selecione NuGet Package ManagerPackage Manager>


Console .

No PMC (Console do Gerenciador de Pacotes), Insira os seguintes comandos:

PowerShell

Add-Migration InitialCreate
Update-Database

Add-Migration InitialCreate : gera um arquivo de

Migrations/{timestamp}_InitialCreate.cs migração. O argumento


InitialCreate é o nome da migração. Qualquer nome pode ser usado, mas,

por convenção, um nome que descreve a migração é selecionado. Como essa


é a primeira migração, a classe gerada contém o código para criar o esquema
de banco de dados. O esquema de banco de dados é baseado no modelo
especificado na classe MvcMovieContext .

Update-Database : Atualizações o banco de dados para a migração mais

recente, que o comando anterior criou. Esse comando executa o Up método


no Migrations/{time-stamp}_InitialCreate.cs arquivo , que cria o banco de
dados.

O Update-Database comando gera 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()'.
Ignore o aviso anterior, corrigido em um tutorial posterior.

Para obter mais informações sobre as ferramentas pmc para EF Core, consulte EF
Core referência de ferramentas – PMC no Visual Studio.

Testar o aplicativo
Execute o aplicativo e selecione o link Aplicativo de Filme .

Se você receber uma exceção semelhante à seguinte, talvez tenha perdido o dotnet ef
database update comando na etapa de migrações:

Visual Studio

Console

SqlException: Cannot open database "MvcMovieContext-1" requested by the


login. The login failed.

7 Observação

Talvez você não consiga inserir casas decimais ou vírgulas no campo Price . Para
dar suporte à validação do jQuery para localidades 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 .

Examinar a classe de contexto e o registro de banco de


dados gerados
Com EF Coreo , o acesso a dados é executado usando um modelo. Um modelo é feito
de classes de entidade e um objeto de contexto que representa uma sessão com o
banco de dados. O objeto de contexto permite consultar e salvar dados. O contexto de
banco de dados é derivado de Microsoft. EntityFrameworkCore.DbContext e especifica
as entidades a serem incluídas no modelo de dados.

O scaffolding cria a classe de contexto do Data/MvcMovieContext.cs banco de dados:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;

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

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


}
}

O código anterior cria uma propriedade DbSet<Movie> que representa os filmes no


banco de dados.

Injeção de dependência
O ASP.NET Core foi criado com a DI (injeção de dependência). Serviços, como o
contexto do banco de dados, são registrados com DI no Program.cs . Esses serviços são
fornecidos a componentes que os exigem por meio de parâmetros de construtor.

Controllers/MoviesController.cs No arquivo , o construtor usa Injeção de Dependência

para injetar o contexto do MvcMovieContext banco de dados no controlador. O contexto


de banco de dados é usado em cada um dos métodos CRUD no controlador.

O scaffolding gerou o seguinte código realçado em Program.cs :

Visual Studio

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
O sistema de configuração ASP.NET Core lê a cadeia de conexão de banco de dados
"MvcMovieContext".

Examinar a cadeia de conexão de banco de dados gerada


O scaffolding adicionou uma cadeia de conexão ao appsettings.json arquivo:

Visual Studio

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=MvcMovieContext-
7dc5;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Para desenvolvimento local, o sistema de configuração ASP.NET Core lê a


ConnectionString chave do appsettings.json arquivo.

A classe InitialCreate
Examine o Migrations/{timestamp}_InitialCreate.cs arquivo de migração:

C#

using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace MvcMovie.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)",
nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2",
nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)",
nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)",
nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}

protected override void Down(MigrationBuilder migrationBuilder)


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

No código anterior:

InitialCreate.Up cria a tabela Movie e configura Id como a chave primária.

InitialCreate.Down reverte as alterações de esquema feitas pela Up migração.

Injeção de dependência no controlador


Abra o Controllers/MoviesController.cs arquivo e examine o construtor:

C#

public class MoviesController : Controller


{
private readonly MvcMovieContext _context;

public MoviesController(MvcMovieContext context)


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

Teste a página Criar. Inserir e enviar dados.

Teste os links Editar, Detalhes e Excluir.

Modelos fortemente tipado e a @model diretiva


Anteriormente neste tutorial, você viu como um controlador pode passar dados ou
objetos para uma exibição usando o dicionário ViewData . O dicionário ViewData é um
objeto dinâmico que fornece uma maneira conveniente de associação tardia para passar
informações para uma exibição.

O MVC fornece a capacidade de passar objetos de modelo fortemente tipado para uma
exibição. Essa abordagem fortemente tipada permite a verificação de código em tempo
de compilação. O mecanismo de scaffolding passou um modelo fortemente tipado na
classe e nas MoviesController exibições.

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

C#

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

var movie = await _context.Movie


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

return View(movie);
}

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


https://localhost:5001/movies/details/1 define:

O controlador para o movies controlador, o primeiro segmento de URL.


A ação para details , o segundo segmento de URL.
O id para 1, o último segmento de URL.

O id pode ser passado com uma cadeia de caracteres de consulta, como no exemplo a
seguir:

https://localhost:5001/movies/details?id=1

O id parâmetro é definido como um tipo anulável ( int? ) nos casos em que o id valor
não é fornecido.

Uma expressão lambda é passada para o FirstOrDefaultAsync método para selecionar


entidades de filme que correspondem aos dados de rota ou ao valor da cadeia de
caracteres de consulta.

C#

var movie = await _context.Movie


.FirstOrDefaultAsync(m => m.Id == id);

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

C#

return View(movie);

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

CSHTML

@model MvcMovie.Models.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>

A instrução @model na parte superior do arquivo de exibição especifica o tipo de objeto


que a exibição espera. Quando o controlador de filme foi criado, a seguinte instrução
@model foi incluída:

CSHTML

@model MvcMovie.Models.Movie

Essa diretiva @model permite o acesso ao filme que o controlador passou para a
exibição. O objeto Model é fortemente tipado. Por exemplo, no modo de exibição
Details.cshtml , o código passa cada campo de filme para os DisplayNameFor Auxiliares
html e DisplayFor com o objeto fortemente tipado Model . 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 Index método 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:

C#
// GET: Movies
public async Task<IActionResult> Index(string searchString)
{
return _context.Movie != null ?
View(await _context.Movie.ToListAsync()) :
Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

O código retornará detalhes do problema se a Movie propriedade do contexto de dados


for nula.

Quando o controlador de filmes foi criado, o scaffolding incluiu a seguinte @model


instrução na parte superior do Index.cshtml arquivo:

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

A @model diretiva permite acesso à lista de filmes que o controlador passou para a
exibição usando um Model objeto fortemente tipado. Por exemplo, no modo de
exibição Index.cshtml , o código percorre os filmes com uma foreach instrução sobre o
objeto fortemente tipado Model :

CSHTML

@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 Model objeto é fortemente tipado como um IEnumerable<Movie> objeto , cada


item no loop é digitado como Movie . Entre outros benefícios, o compilador valida os
tipos usados no código.

Recursos adicionais
Entity Framework Core para iniciantes
Auxiliares de Marcas
Globalização e localização

Anterior: adicionando uma exibição a seguir: trabalhando com SQL


Parte 5, trabalhe com um banco de
dados em um aplicativo MVC ASP.NET
Core
Artigo • 02/12/2022 • 15 minutos para o fim da leitura

Por Rick Anderson e Jon P Smith .

O objeto MvcMovieContext cuida da tarefa de se conectar ao banco de dados e mapear


objetos Movie para registros do banco de dados. O contexto do banco de dados é
registrado com o contêiner injeção de dependência no Program.cs arquivo:

Visual Studio

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

O sistema de configuração de ASP.NET Core lê a ConnectionString chave. Para


desenvolvimento local, ele obtém a cadeia de conexão do appsettings.json
arquivo:

JSON

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

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 SQL
Server de produção. Para obter mais informações, confira Configuração.

Visual Studio
SQL Server Express LocalDB
LocalDB:

É uma versão leve do Mecanismo de Banco de Dados SQL Server Express,


instalada por padrão com o Visual Studio.
Inicia sob demanda usando uma cadeia de conexão.
É direcionado para o desenvolvimento de programas. Ele é executado no
modo de usuário, portanto, não há configuração complexa.
Por padrão, cria arquivos .mdf no diretório C:/Users/{user} .

Propagar o banco de dados


Crie uma nova classe chamada SeedData na pasta Models. Substitua o código gerado
pelo seguinte:

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MvcMovie.Data;
using System;
using System.Linq;

namespace MvcMovie.Models;

public static class SeedData


{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-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 filmes no banco de dados, o inicializador de semente retornará e nenhum


filme será adicionado.

C#

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

Adicionar o inicializador de semeadura

Visual Studio

Substitua o conteúdo de Program.cs pelo seguinte código. O novo código está


realçado.

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MvcMovie.Data;
using MvcMovie.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

// Add services to the container.


builder.Services.AddControllersWithViews();

var app = builder.Build();

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


{
var services = scope.ServiceProvider;

SeedData.Initialize(services);
}

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Exclua todos os registros no banco de dados. Faça isso com os links Excluir no
navegador ou no SSOX.

Testar o aplicativo. Force o aplicativo a inicializar, chamando o código no


Program.cs arquivo para que o método de semente seja executado. Para forçar a

inicialização, feche a janela do prompt de comando que o Visual Studio abriu e


reinicie pressionando Ctrl+F5.
O aplicativo mostra os dados propagados.

Anterior: adicionando um modelo

Next: adicionando métodos e exibições do controlador


Parte 6, métodos de controlador e
exibições no ASP.NET Core
Artigo • 02/12/2022 • 27 minutos para o fim da leitura

De Rick Anderson

Temos um bom começo para o aplicativo de filme, mas a apresentação não é ideal. Por
exemplo, ReleaseDate deveria ser separado em duas palavras.

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

C#

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

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}

DataAnnotations são explicados 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 de Âncora MVC
Principal no Views/Movies/Index.cshtml arquivo.
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 HTML href do método de

ação do controlador e da ID de rota. Use Exibir Fonte do navegador favorito ou use as


ferramentas de desenvolvedor para examinar a marcação gerada. Uma parte do HTML
gerado é mostrada abaixo:

HTML

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

Lembre-se do formato para o conjunto de roteamento no Program.cs arquivo:

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

O ASP.NET Core converte https://localhost:5001/Movies/Edit/4 de uma solicitação no


método de ação Edit do controlador Movies com o parâmetro Id igual a 4. (Os
métodos do controlador também são conhecidos como métodos de ação.)

Os Auxiliares de Marcação são um dos novos recursos mais populares do ASP.NET Core.
Para obter mais informações, consulte Recursos adicionais.

Abra o controlador Movies e examine os dois métodos de ação Edit . O código a seguir
mostra o HTTP GET Edit método , que busca o filme e preenche o formulário de edição
gerado pelo Edit.cshtml Razor arquivo.

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

var movie = await _context.Movie.FindAsync(id);


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

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

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, 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,Rating")] 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);
}

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

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, 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,Rating")] 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);
}

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 ValidateAntiForgeryToken atributo é usado para impedir a falsificação de uma


solicitação e é emparelhado com um token anti-falsificaçã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.

CSHTML

<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 Impedir ataques de

XSRF/CSRF (solicitação intersite forjada) no ASP.NET Core.

O método HttpGet Edit usa o parâmetro ID de filme, pesquisa o filme usando o


método FindAsync 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.

C#

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

var movie = await _context.Movie.FindAsync(id);


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

Quando o sistema de scaffolding criou a exibição de Edição, ele examinou a classe


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

CSHTML

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

HTML

<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="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmq
UyXnJBXhmrjcUVDJyDUMm7-
MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

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

Processando a solicitação POST


A lista a seguir mostra a versão [HttpPost] do método de ação Edit .

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, 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,Rating")] 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);
}

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 . A ModelState.IsValid propriedade
verifica se os dados enviados no formulário podem ser usados para modificar (editar ou
atualizar) um Movie objeto. 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 de Validação no Views/Movies/Edit.cshtml
modelo de exibição cuida da exibição de 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 HTTP GET método também viola as práticas recomendadas de HTTP e o padrão de
arquitetura REST , 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
Impedir ataques XSRF/CSRF (solicitação entre sites) em ASP.NET Core
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
Auxiliar de Marcação de seleção
Auxiliar de marcação de validação

Anterior Avançar
Parte 7, adicionar pesquisa a um
aplicativo MVC ASP.NET Core
Artigo • 02/12/2022 • 23 minutos para o fim da leitura

De Rick Anderson

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

Atualize o Index método encontrado dentro Controllers/MoviesController.cs com o


seguinte código:

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

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

return View(await movies.ToListAsync());


}

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

C#

var movies = from m in _context.Movie


select m;

A consulta só é 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:
C#

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

O código s => s.Title!.Contains(searchString) acima é uma Expressão Lambda.


Lambdas são usados em consultas LINQ baseadas em método como argumentos para
métodos de operador de consulta padrão, como o Where método 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 Contains método é 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 até /Movies/Index . Acrescente uma cadeia de consulta, como ?


searchString=Ghost , à URL. Os filmes filtrados são exibidos.
Se você alterar a assinatura do Index método para ter um parâmetro chamado id , o id
parâmetro corresponderá ao espaço reservado opcional {id} para as rotas padrão
definidas em Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

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

O método Index anterior:

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

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

return View(await movies.ToListAsync());


}

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

C#

public async Task<IActionResult> Index(string id)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

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

return View(await movies.ToListAsync());


}

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

No entanto, você não pode esperar que os usuários modifiquem a URL sempre que
desejarem pesquisar um filme. 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 :

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


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

return View(await movies.ToListAsync());


}

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


abaixo:

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

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

<h2>Index</h2>

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

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


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

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.

C#

[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:
{PORT}/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. Corrija isso especificando que a solicitação deve ser
HTTP GET encontrada no Views/Movies/Index.cshtml arquivo .

CSHTML

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

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

CSHTML

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

Adicionar pesquisa por gênero


Adicione a seguinte classe MovieGenreViewModel à pasta Models:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models;

public class MovieGenreViewModel


{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}

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

Uma lista de filmes.


Uma SelectList que contém a lista de gêneros. Isso permite que o usuário
selecione um gênero na lista.
MovieGenre , que contém o gênero selecionado.
SearchString , que contém o texto que os usuários inserem na caixa de texto de

pesquisa.

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

C#

// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string
searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;

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

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

var movieGenreVM = new MovieGenreViewModel


{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};

return View(movieGenreVM);
}

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

C#

// Use LINQ to get list of genres.


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

A SelectList de gêneros é criada com a projeção dos gêneros distintos (não desejamos
que nossa lista de seleção tenha gêneros duplicados).

Quando o usuário pesquisa o item, o valor de pesquisa é mantido na caixa de pesquisa.

Adicionar pesquisa por gênero à exibição


Índice
Atualize Index.cshtml , encontrado em Views/Movies/, da seguinte maneira:

CSHTML
@model MvcMovie.Models.MovieGenreViewModel

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

<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.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 auxiliar HTML a seguir:

@Html.DisplayNameFor(model => model.Movies![0].Title)

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


referenciada na expressão lambda para determinar o nome de exibição. Como a
expressão lambda é inspecionada em vez de avaliada, você não recebe uma violação de
acesso quando model , model.Movies ou model.Movies[0] é null ou vazio. Quando a
expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem =>
item.Title) ), os valores da propriedade do modelo são avaliados. O ! após

model.Movies é o operador tolerante a nulo, que é usado para declarar que Movies não
é nulo.

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


Anterior Avançar
Parte 8, adicionar um novo campo a um
aplicativo MVC ASP.NET Core
Artigo • 02/12/2022 • 21 minutos para o fim da leitura

De Rick Anderson

Nesta seção, as Migrações do Entity Framework Code First são usadas para:

Adicionar um novo campo ao modelo.


Migrar o novo campo para o banco de dados.

Ao usar o Code First do EF para criar automaticamente um banco de dados, o Code


First:

Adiciona uma tabela ao banco de dados para acompanhar o esquema do banco


de dados.
Verifica se o banco de dados está em sincronia com as classes de modelo das
quais foi gerado. Se ele não estiver sincronizado, o EF gerará uma exceção. Isso
facilita a descoberta de problemas de código/banco de dados inconsistente.

Adicionar uma Propriedade de Classificação ao


Modelo de Filme
Adicione uma Rating propriedade a Models/Movie.cs :

C#

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

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }

[Display(Name = "Release Date")]


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

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
public string? Rating { get; set; }
}

Compilar o aplicativo

Visual Studio

Ctrl+Shift+B

Como você adicionou um novo campo à Movie classe , é necessário atualizar a lista de
associação de propriedades para que essa nova propriedade seja incluída. No
MoviesController.cs , atualize o [Bind] atributo para os Create métodos de ação e

Edit para incluir a Rating propriedade :

C#

[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 /Views/Movies/Index.cshtml arquivo e adicione um Rating campo:

CSHTML

<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<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>

Atualize o /Views/Movies/Create.cshtml com um Rating campo .

Visual Studio/Visual Studio para Mac

Copie/cole o “grupo de formulário” anterior e permita que o IntelliSense ajude você


a atualizar os campos. O IntelliSense funciona com os Auxiliares de Marcação.
Atualize os modelos restantes.

Atualize a classe SeedData para que ela forneça um valor para a nova coluna. Uma
alteração de amostra é mostrada abaixo, mas é recomendável fazer essa alteração em
cada new Movie .

C#

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

No menu Ferramentas , selecione Console do Gerenciador de Pacotes do


Gerenciador de Pacotes > NuGet.

No PMC, insira os seguintes comandos:


PowerShell

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 e exibir filmes com um Rating
campo.

Anterior Avançar
Parte 9, adicionar validação a um
aplicativo MVC ASP.NET Core
Artigo • 03/12/2022 • 30 minutos para o fim da leitura

De Rick Anderson

Nesta seção:

A lógica de validação é adicionada ao modelo Movie .


Você garante que as regras de validação sejam impostas sempre que um usuário
criar ou editar um filme.

Mantendo o processo DRY


Um dos princípios de design do MVC é o DRY (“Don't Repeat Yourself”). O ASP.NET
Core MVC incentiva você a especificar a funcionalidade ou o comportamento somente
uma vez e, em seguida, refleti-lo em qualquer lugar de um aplicativo. Isso reduz a
quantidade de código que você precisa escrever e faz com que o código escrito seja
menos propenso a erros e mais fácil de testar e manter.

O suporte de validação fornecido pelo MVC e pelo Entity Framework Core Code First é
um bom exemplo do princípio DRY em ação. Especifique as regras de validação de
forma declarativa em um único lugar (na classe de modelo) e as regras são impostas em
qualquer lugar no aplicativo.

Adicionar regras de validação ao modelo de


filme
O namespace DataAnnotations fornece um conjunto de atributos de validação internos
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 internos.

C#

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

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


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

[Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
[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 anterior, "Gênero":
Deve usar apenas letras.
A primeira letra deve ser maiúscula. Espaços em branco são permitidos
enquanto números e caracteres especiais não são permitidos.

A "Classificação" RegularExpression :
Exige que o primeiro caractere seja uma letra maiúscula.
Permite caracteres especiais e números nos espaços subsequentes. "PG-13" é
válido para uma classificação, mas é um erro em "Gênero".
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.

Interface do usuário de erro de validação


Execute o aplicativo e navegue para o controlador Movies.

Selecione o link Criar Novo para adicionar um novo filme. Preencha o formulário com
alguns valores inválidos. Assim que a validação do lado do cliente do jQuery detecta o
erro, ela exibe uma mensagem de erro.
7 Observação

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. Consulte
este comentário do GitHub 4076 para obter instruções sobre como adicionar
vírgula decimal.
Observe como o formulário renderizou automaticamente uma mensagem de erro de
validação apropriada em cada campo que contém um valor inválido. Os erros são
impostos no lado do cliente (usando o JavaScript e o jQuery) e no lado do servidor (caso
um usuário tenha o JavaScript desabilitado).

Um benefício significativo é que você não precisava alterar uma única linha de código
na MoviesController classe ou no Create.cshtml modo de exibição para habilitar essa
interface do usuário de validação. O controlador e as exibições criados anteriormente
neste tutorial selecionaram automaticamente as regras de validação especificadas com
atributos de validação nas propriedades da classe de modelo Movie . Teste a validação
usando o método de ação Edit e a mesma validação é aplicada.

Os dados de formulário não serão enviados para o servidor enquanto houver erros de
validação do lado do cliente. Verifique isso colocando um ponto de interrupção no
método HTTP Post usando a ferramenta Fiddler ou as ferramentas do Desenvolvedor
F12.

Como funciona a validação


Talvez você esteja se perguntando como a interface do usuário de validação foi gerada
sem atualizações do código no controlador ou nas exibições. O código a seguir mostra
os dois métodos Create .

C#

// GET: Movies/Create
public IActionResult Create()
{
return View();
}

// POST: Movies/Create
// To protect from overposting attacks, 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>
Create([Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(movie);
}

O primeiro método de ação (HTTP GET) Create exibe o formulário Criar inicial. A
segunda versão ( [HttpPost] ) manipula a postagem de formulário. O segundo método
Create (a versão [HttpPost] ) chama ModelState.IsValid para verificar se o filme tem

erros de validação. A chamada a esse método avalia os atributos de validação que


foram aplicados ao objeto. Se o objeto tiver erros de validação, o método Create
exibirá o formulário novamente. Se não houver erros, o método salvará o novo filme no
banco de dados. Em nosso exemplo de filme, o formulário não é postado no servidor
quando há erros de validação detectados no lado do cliente; o segundo método Create
nunca é chamado quando há erros de validação do lado do cliente. Se você desabilitar o
JavaScript no navegador, a validação do cliente será desabilitada e você poderá testar o
método Create HTTP POST ModelState.IsValid detectando erros de validação.

Defina um ponto de interrupção no método [HttpPost] Create e verifique se o método


nunca é chamado; a validação do lado do cliente não enviará os dados de formulário
quando forem detectados erros de validação. Se você desabilitar o JavaScript no
navegador e, em seguida, enviar o formulário com erros, o ponto de interrupção será
atingido. Você ainda pode obter uma validação completa sem o JavaScript.

A imagem a seguir mostra como desabilitar o JavaScript no navegador Firefox.


A imagem a seguir mostra como desabilitar o JavaScript no navegador Chrome.
Depois de desabilitar o JavaScript, poste os dados inválidos e execute o depurador em
etapas.
Uma parte do Create.cshtml modelo de exibição é mostrada na seguinte marcação:

HTML

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

@*Markup removed for brevity.*@

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.

Usando atributos DataType


Abra o Movie.cs arquivo e examine a Movie classe . O namespace
System.ComponentModel.DataAnnotations fornece atributos de formatação, além do

conjunto interno de atributos de validação. Já aplicamos um valor de enumeração


DataType à data de lançamento e aos campos de preço. O código a seguir mostra as
propriedades ReleaseDate e Price com o atributo DataType apropriado.

C#

[Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

Os DataType atributos fornecem apenas dicas para o mecanismo de exibição formatar


os dados e fornece elementos/atributos, como <a> para URLs e para email <a
href="mailto:EmailAddress.com"> . 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:

C#

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


true)]
public DateTime ReleaseDate { get; set; }

A configuração ApplyFormatInEditMode especifica que a formatação também deve ser


aplicada quando o valor é exibido em uma caixa de texto para edição. (Talvez você não
deseje isso em alguns campos – por exemplo, para valores de moeda, provavelmente,
você não deseja exibir o símbolo de moeda na caixa de texto para edição.)

Você pode usar o atributo DisplayFormat por si só, mas geralmente é uma boa ideia
usar o atributo DataType . O atributo DataType transmite a semântica dos dados, ao
invés de apresentar como renderizá-lo em uma tela e oferece os seguintes benefícios
que você não obtém com DisplayFormat:

O navegador pode habilitar os recursos do HTML5 (por exemplo, mostrar um


controle de calendário, o símbolo de moeda apropriado à localidade, links de
email, etc.)

Por padrão, o navegador renderizará os dados usando o formato correto de


acordo com a localidade.

O atributo DataType pode permitir que o MVC escolha o modelo de campo


correto para renderizar os dados (o DisplayFormat , se usado por si só, usa o
modelo de cadeia de caracteres).

7 Observação

A validação do jQuery não funciona com os atributos Range e DateTime . Por


exemplo, o seguinte código sempre exibirá um erro de validação do lado do
cliente, mesmo quando a data estiver no intervalo especificado:

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


Você precisará desabilitar a validação de data do jQuery para usar o atributo Range com
DateTime . Geralmente, não é uma boa prática compilar datas rígidas nos modelos e,
portanto, o uso do atributo Range e de DateTime não é recomendado.

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

C#

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

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$"), Required, StringLength(30)]
public string Genre { get; set; }
[Range(1, 100), DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[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
Trabalhar com formulários
Globalização e localização
Introdução aos auxiliares de marcação
Auxiliares de marca de autor

Anterior Avançar
Parte 10, examine os métodos Detalhes
e Excluir de um aplicativo ASP.NET Core
Artigo • 02/12/2022 • 9 minutos para o fim da leitura

De Rick Anderson

Abra o controlador Movie e examine o método Details :

C#

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

var movie = await _context.Movie


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

return View(movie);
}

O mecanismo de scaffolding MVC que criou este método de ação adiciona um


comentário mostrando uma solicitação HTTP que invoca o método. Nesse caso, é uma
solicitação GET com três segmentos de URL, o controlador Movies , o método Details e
um valor id . Lembre-se de que esses segmentos são definidos em Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

O EF facilita a pesquisa de dados usando o método FirstOrDefaultAsync . Um recurso


de segurança importante interno do método é que o código verifica se o método de
pesquisa encontrou um filme antes de tentar fazer algo com ele. Por exemplo, um
hacker pode introduzir erros no site alterando a URL criada pelos links de
http://localhost:{PORT}/Movies/Details/1 para algo como http://localhost:
{PORT}/Movies/Details/12345 (ou algum outro valor que não representa um filme real).

Se você não marcou um filme nulo, o aplicativo gerará uma exceção.

Examine os métodos Delete e DeleteConfirmed .

C#

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

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

var movie = await _context.Movie


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

return View(movie);
}

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

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:

C#

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

C#

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{

O CLR (Common Language Runtime) exige que os métodos sobrecarregados tenham


uma assinatura de parâmetro exclusiva (mesmo nome de método, mas uma lista
diferente de parâmetros). No entanto, aqui você precisa de dois métodos Delete – um
para GET e outro para POST – que têm a mesma assinatura de parâmetro. (Ambos
precisam aceitar um único inteiro como parâmetro.)

Há duas abordagens para esse problema e uma delas é fornecer aos métodos nomes
diferentes. Foi isso o que o mecanismo de scaffolding fez no exemplo anterior. No
entanto, isso apresenta um pequeno problema: o ASP.NET mapeia os segmentos de
URL para os métodos de ação por nome e se você renomear um método, o roteamento
normalmente não conseguirá encontrar esse método. A solução é o que você vê no
exemplo, que é adicionar o atributo ActionName("Delete") ao método DeleteConfirmed .
Esse atributo executa o mapeamento para o sistema de roteamento, de modo que uma
URL que inclui /Delete/ para uma solicitação POST encontre o método DeleteConfirmed .

Outra solução alternativa comum para métodos que têm nomes e assinaturas idênticos
é alterar artificialmente a assinatura do método POST para incluir um parâmetro extra
(não utilizado). Foi isso o que fizemos em uma postagem anterior quando adicionamos
o parâmetro notUsed . Você pode fazer o mesmo aqui para o método [HttpPost]
Delete :

C#

// POST: Movies/Delete/6
[HttpPost]
[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 ASP.NET Core e Banco de Dados SQL no Serviço de Aplicativo do Azure.

Anterior
Exibições no ASP.NET Core MVC
Artigo • 28/11/2022 • 20 minutos para o fim da leitura

Por Steve Smith e Dave Brock

Este documento explica as exibições usadas em aplicativos do ASP.NET Core MVC. Para
obter informações sobre Razor páginas, consulte Introdução às Razor Páginas no
ASP.NET Core.

No padrão MVC (Model-View-Controller), a exibição trata da apresentação de dados do


aplicativo e da interação com o usuário. Uma exibição é um modelo HTML com
marcação inseridaRazor. Razor marcação é o código que interage com a marcação
HTML para produzir uma página da Web que é enviada ao cliente.

Em ASP.NET Core MVC, as exibições são .cshtml arquivos que usam a linguagem de
programação C# na Razor marcação. Geralmente, arquivos de exibição são agrupados
em pastas nomeadas para cada um dos controladores do aplicativo. As pastas são
armazenadas em uma Views pasta na raiz do aplicativo:

O Home controlador é representado por uma Home pasta dentro da Views pasta. A Home
pasta contém as exibições das páginas da About Web , Contact e Index (homepage).
Quando um usuário solicita uma dessas três páginas da Web, as ações do Home
controlador no controlador determinam qual das três exibições é usada para criar e
retornar uma página da Web ao 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 o model binding, 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 ao model binding para produzir a saída
da página da Web.

Benefícios do uso de exibições


As exibições ajudam a estabelecer uma separação de Interesses dentro de um aplicativo
MVC, separando a marcação da interface do usuário de outras partes do aplicativo.
Seguir um design de SoC faz com que seu aplicativo seja modular, o que fornece vários
benefícios:

A manutenção do aplicativo é mais fácil, porque ele é melhor organizado.


Geralmente, as exibições são agrupadas segundo os recursos do aplicativo. Isso
facilita encontrar exibições relacionadas ao trabalhar em um recurso.
As partes do aplicativo ficam acopladas de forma flexível. Você pode compilar e
atualizar as exibições do aplicativo separadamente da lógica de negócios e dos
componentes de acesso a dados. É possível modificar os modos de exibição do
aplicativo sem precisar necessariamente atualizar outras partes do aplicativo.
É mais fácil testar as partes da interface do usuário do aplicativo porque as
exibições são unidades separadas.
Devido à melhor organização, é menos provável que você repita acidentalmente
seções da interface do usuário.

Criando uma exibição


As exibições específicas de um controlador são criadas na Views/[ControllerName]
pasta. As exibições compartilhadas entre controladores são colocadas na Views/Shared
pasta. Para criar um modo de exibição, adicione um novo arquivo e dê a ele o mesmo
nome que sua ação de controlador associada com a extensão de .cshtml arquivo. Para
criar uma exibição que corresponda à ação About no Home controlador, crie um
About.cshtml arquivo na Views/Home pasta:

CSHTML

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

<p>Use this area to provide additional information.</p>

Razor A marcação começa com o @ símbolo. Execute instruções C# colocando o código


C# dentro Razor de blocos de código definidos por chaves ( { ... } ). Por exemplo,
consulte a atribuição de "About" para ViewData["Title"] mostrado acima. É possível
exibir valores em HTML simplesmente referenciando o valor com o símbolo @ . Veja o
conteúdo dos elementos <h2> e <h3> acima.

O conteúdo da exibição mostrado acima é apenas uma parte da página da Web inteira
que é renderizada para o usuário. O restante do layout da página e outros aspectos
comuns da exibição são especificados em outros arquivos de exibição. Para saber mais,
consulte o tópico sobre Layout.

Como controladores especificam exibições


Normalmente, os modos de exibição são retornados de ações como um ViewResult, que
é um tipo de ActionResult. O método de ação pode criar e retornar um ViewResult
diretamente, mas normalmente isso não é feito. Como a maioria dos controladores
herda, Controllervocê simplesmente usa o View método auxiliar para retornar o
ViewResult :

HomeController.cs :

C#

public IActionResult About()


{
ViewData["Message"] = "Your application description page.";

return View();
}
Quando essa ação retorna, o About.cshtml modo de exibição mostrado na última seção
é renderizado 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:

C#

return View("Orders");

Um modelo a ser passado para a exibição:

C#

return View(Orders);

Um modo de exibição e um modelo:

C#

return View("Orders", Orders);

Descoberta de exibição
Quando uma ação retorna uma exibição, um processo chamado descoberta de exibição
ocorre. Esse processo determina qual arquivo de exibição é usado com base no nome
da exibição.
O comportamento padrão do método View ( return View(); ) é retornar uma exibição
com o mesmo nome que o método de ação do qual ela é chamada. Por exemplo, o
About ActionResult nome do método do controlador é usado para pesquisar um

arquivo de exibição chamado About.cshtml . Primeiro, o runtime procura na


Views/[ControllerName] pasta o modo de exibição. Se ele não encontrar uma exibição

correspondente lá, ele pesquisará a Shared pasta para a exibição.

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/\[ViewName].cshtml

Um caminho de arquivo de exibição pode ser fornecido em vez de um nome de


exibição. Se estiver usando um caminho absoluto começando na raiz do aplicativo
(opcionalmente começando com "/" ou "~/"), a .cshtml extensão deve ser especificada:

C#

return View("Views/Home/About.cshtml");

Você também pode usar um caminho relativo para especificar exibições em diretórios
diferentes sem a .cshtml extensão. Dentro do HomeController , você pode retornar a
exibição Index de seus Manage modos de exibição com um caminho relativo:

C#

return View("../Manage/Index");

De forma semelhante, você pode indicar o atual diretório específico do controlador com
o prefixo ". /":

C#

return View("./About");

Exibições parciais e componentes de exibição usam mecanismos de descoberta


semelhantes (mas não idênticos).
Você pode personalizar a convenção padrão de como as exibições estão localizadas no
aplicativo usando um personalizado IViewLocationExpander.

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.

Passar dados para exibições


Passe dados para exibições usando várias abordagens:

Dados fortemente tipados: viewmodel


Dados fracamente tipados
ViewData ( ViewDataAttribute )
ViewBag

Dados fortemente tipados (viewmodel)


A abordagem mais robusta é especificar um tipo de modelo na exibição. Esse modelo é
conhecido como viewmodel. Você passa uma instância do tipo viewmodel para a
exibição da ação.

Usar um viewmodel para passar dados para uma exibição permite que a exibição tire
proveito da verificação de tipo forte. Tipagem forte (ou fortemente tipado) significa que
cada variável e constante têm um tipo definido explicitamente (por exemplo, string ,
int ou DateTime ). A validade dos tipos usados em uma exibição é verificada em tempo

de compilação.

O Visual Studio e o Visual Studio Code listam membros de classe fortemente


tipados usando um recurso chamado IntelliSense. Quando quiser ver as propriedades
de um viewmodel, digite o nome da variável para o viewmodel, seguido por um ponto
final ( . ). Isso ajuda você a escrever código mais rapidamente e com menos erros.
Especifique um modelo usando a diretiva @model . Use o modelo com @Model :

CSHTML

@model WebApplication1.ViewModels.Address

<h2>Contact</h2>
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>

Para fornecer o modelo à exibição, o controlador o passa como um parâmetro:

C#

public IActionResult Contact()


{
ViewData["Message"] = "Your contact page.";

var viewModel = new Address()


{
Name = "Microsoft",
Street = "One Microsoft Way",
City = "Redmond",
State = "WA",
PostalCode = "98052-6399"
};

return View(viewModel);
}

Não há restrições quanto aos tipos de modelo que você pode fornecer a uma exibição.
Recomendamos o uso de viewmodels do tipo POCO (objeto CRL básico) com pouco ou
nenhum comportamento (métodos) definido. Normalmente, as classes viewmodel são
armazenadas na Models pasta ou em uma pasta separada ViewModels na raiz do
aplicativo. O Address modo de exibição usado no exemplo acima é um modo de
exibição POCO armazenado em um arquivo chamado Address.cs :

C#

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 model binding e validação para
dados enviados ao aplicativo pelo usuário.

Dados com tipo fraco ( ViewData , [ViewData] atributo e


ViewBag )

ViewBag não está disponível por padrão para uso em Razor Classes de páginas PageModel .

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.

Passar dados entre... Exemplo

Um controlador e uma Preencher uma lista suspensa com os dados.


exibição

Uma exibição e uma Definindo o conteúdo do <title> elemento no modo de exibição de


exibição de layout layout de um arquivo de exibição.

Uma exibição parcial e Um widget que exibe dados com base na página da Web que o
uma exibiçã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. Observação: as
pesquisas de chave não diferenciam maiúsculas de minúsculas para ambos ViewData e
ViewBag .
ViewData e ViewBag são resolvidos dinamicamente em runtime. 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 ViewDataDictionary objeto acessado por meio de string chaves. 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:

C#

public IActionResult SomeAction()


{
ViewData["Greeting"] = "Hello";
ViewData["Address"] = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};

return View();
}

Trabalhar com os dados em uma exibição:

CSHTML

@{
// Since Address isn't a string, it requires a cast.
var address = ViewData["Address"] as Address;
}

@ViewData["Greeting"] World!

<address>
@address.Name<br>
@address.Street<br>
@address.City, @address.State @address.PostalCode
</address>

Atributo [ViewData]
Outra abordagem que usa o ViewDataDictionary é ViewDataAttribute. Propriedades em
controladores ou Razor modelos de página marcadas com o [ViewData] atributo têm
seus valores armazenados e carregados do dicionário.

No exemplo a seguir, o Home controlador contém uma Title propriedade marcada


com [ViewData] . O método About define o título para a exibição About:

C#

public class HomeController : Controller


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

public IActionResult About()


{
Title = "About Us";
ViewData["Message"] = "Your application description page.";

return View();
}
}

No layout, o título é lido a partir do dicionário ViewData:

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...

ViewBag

ViewBag não está disponível por padrão para uso em Razor Classes de páginas PageModel .

ViewBag é um Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData objeto


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:

C#

public IActionResult SomeAction()


{
ViewBag.Greeting = "Hello";
ViewBag.Address = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};

return View();
}

CSHTML

@ViewBag.Greeting World!

<address>
@ViewBag.Address.Name<br>
@ViewBag.Address.Street<br>
@ViewBag.Address.City, @ViewBag.Address.State
@ViewBag.Address.PostalCode
</address>

Usando ViewData e ViewBag simultaneamente

ViewBag não está disponível por padrão para uso em Razor Classes de páginas PageModel .

Como ViewData e ViewBag fazem referência à mesma coleção ViewData subjacente,


você pode usar ViewData e ViewBag , além de misturá-los e combiná-los ao ler e gravar
valores.

Defina o uso ViewBag do título e a descrição usando ViewData na parte superior de um


modo de exibição About.cshtml :

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 . _Layout.cshtml No


arquivo, obtenha o título usando ViewData e obtenha a descrição usando ViewBag :

CSHTML

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

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>About Contoso</title>
<meta name="description" content="Let us tell you about Contoso's
philosophy and mission.">
...

Resumo das diferenças entre ViewData e ViewBag

ViewBag não está disponível por padrão para uso em Razor Classes de páginas PageModel .

ViewData

Deriva de ViewDataDictionary, portanto, ele tem propriedades de dicionário que


podem ser úteis, como ContainsKey , , Add e Remove Clear .
Chaves no dicionário são cadeias de caracteres, de forma que espaços em
branco são permitidos. Exemplo: ViewData["Some Key With Whitespace"]
Qualquer tipo diferente de string deve ser convertido na exibição para usar
ViewData .
ViewBag

Deriva de Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData ,
portanto, permite a criação de propriedades dinâmicas usando notação de
ponto ( @ViewBag.SomeKey = <value or object> ) e nenhuma conversão é
necessária. A sintaxe de ViewBag torna mais rápido adicioná-lo a controladores
e exibições.
Mais simples de verificar quanto à presença de valores nulos. Exemplo:
@ViewBag.Person?.Name

Quando usar ViewData ou ViewBag


ViewData e ViewBag são abordagens igualmente válidas para passar pequenas

quantidades de dados entre controladores e exibições. A escolha de qual delas usar é


baseada na preferência. Você pode misturar e combinar objetos ViewData e ViewBag ,
mas é mais fácil ler e manter o código quando uma abordagem é usada de maneira
consistente. Ambas as abordagens são resolvidas dinamicamente em runtime e,
portanto, são propensas a causar erros de runtime. Algumas equipes de
desenvolvimento as evitam.

Exibições dinâmicas
Exibições que não declaram um tipo de modelo usando @model , mas que têm uma
instância de modelo passada a elas (por exemplo, return View(Address); ) podem
referenciar as propriedades da instância dinamicamente:

CSHTML

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

Mais recursos das exibições


Auxiliares de Marca facilitam a adição do comportamento do lado do servidor às
marcações HTML existentes. O uso de Auxiliares de marca evita a necessidade de
escrever código personalizado ou auxiliares em suas exibições. Auxiliares de marca são
aplicados como atributos a elementos HTML e são ignorados por editores que não
podem processá-los. Isso permite editar e renderizar a marcação da exibição em várias
ferramentas.

É possível gerar uma marcação HTML personalizada com muitos auxiliares HTML
internos. Uma lógica de interface do usuário mais complexa pode ser tratada por
Componentes de exibição. Componentes de exibição fornecem o mesmo SoC oferecido
por controladores e exibições. Eles podem eliminar a necessidade de ações e exibições
que lidam com os dados usados por elementos comuns da interface do usuário.

Como muitos outros aspectos do ASP.NET Core, as exibições dão suporte à injeção de
dependência, permitindo que serviços sejam injetados em exibições.

Isolamento de CSS
Isole estilos CSS em páginas, exibições e componentes individuais para reduzir ou evitar:

Dependências de estilos globais que podem ser desafiadoras de manter.


Conflitos de estilo em conteúdo aninhado.

Para adicionar um arquivo CSS com escopo para uma página ou exibição, coloque os
estilos CSS em um arquivo .cshtml.css complementar correspondente ao nome do
arquivo .cshtml . No exemplo a seguir, um Index.cshtml.css arquivo fornece estilos
CSS que são aplicados somente à Index.cshtml página ou exibição.

Pages/Index.cshtml.css (Razor Pages) ou Views/Index.cshtml.css (MVC):

css

h1 {
color: red;
}

O isolamento de CSS ocorre no momento do build. A estrutura reescreve seletores de


CSS para fazer a correspondência da marcação renderizada pelas páginas ou exibições
do aplicativo. Os estilos CSS reescritos são empacotados e produzidos como um ativo
estático, {APP ASSEMBLY}.styles.css . O espaço reservado {APP ASSEMBLY} é o nome do
assembly do projeto. Um link para os estilos CSS empacotados é colocado no layout do
aplicativo.

No conteúdo <head> do aplicativo Pages/Shared/_Layout.cshtml (Razor Pages) ou


Views/Shared/_Layout.cshtml (MVC), adicione ou confirme a presença do link para os
estilos CSS empacotados:

HTML

<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />

No seguinte exemplo, o nome do assembly do aplicativo é WebApp :

HTML

<link rel="stylesheet" href="WebApp.styles.css" />

Os estilos definidos em um arquivo CSS com escopo são aplicados apenas à saída
renderizada do arquivo correspondente. No exemplo anterior, as declarações CSS h1
definidas em outro lugar no aplicativo não entram em conflito com o estilo de título de
Index . O estilo CSS em cascata e as regras de herança permanecem em vigor para

arquivos CSS com escopo. Por exemplo, estilos aplicados diretamente a um elemento
<h1> no arquivo Index.cshtml substituem os estilos do arquivo CSS com escopo em

Index.cshtml.css .

7 Observação

Para garantir o isolamento do estilo CSS quando o agrupamento ocorrer, não há


suporte para a importação de CSS em blocos de código de Razor.

O isolamento de CSS se aplica apenas a elementos HTML. Não há suporte para


isolamento de CSS para Auxiliares de Marcação.

Dentro do arquivo CSS empacotado, cada página, exibição ou componente Razor é


associado a um identificador de escopo no formato b-{STRING} , em que o espaço
reservado {STRING} é uma cadeia de caracteres de dez caracteres gerada pela estrutura.
O seguinte exemplo fornece o estilo do elemento <h1> anterior na página Index de um
aplicativo Razor Pages:

css

/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
color: red;
}
Na página Index em que o estilo CSS é aplicada do arquivo empacotado, o
identificador de escopo é acrescentado como um atributo HTML:

HTML

<h1 b-3xxtam6d07>

O identificador é exclusivo para um aplicativo. No momento da compilação, um pacote


de projeto é criado com a convenção {STATIC WEB ASSETS BASE
PATH}/Project.lib.scp.css , em que o espaço reservado {STATIC WEB ASSETS BASE PATH}

é o caminho base dos ativos da Web estáticos.

Se outros projetos forem utilizados, como pacotes NuGet ou bibliotecas de classes


Razor, o arquivo empacotado:

Fará referência aos estilos que usam importações de CSS.


Não será publicado como um ativo da Web estático do aplicativo que consome os
estilos.

Suporte para pré-processador de CSS


Pré-processadores de CSS são úteis para aprimorar o desenvolvimento de CSS
utilizando recursos como variáveis, aninhamento, módulos, mixins e herança. Embora o
isolamento de CSS não dê suporte nativo a pré-processadores CSS, como Sass ou Less,
a integração de pré-processadores de CSS é contínua desde que a compilação do pré-
processador ocorra antes que a estrutura reescreva os seletores de CSS durante o
processo de build. Usando o Visual Studio, por exemplo, configure a compilação de pré-
processador existente como uma tarefa Before Build no Gerenciador do Executor de
Tarefas do Visual Studio.

Muitos pacotes NuGet de terceiros, como Delegate.SassBuilder , podem compilar


arquivos SASS/SCSS no início do processo de build antes que o isolamento de CSS
ocorra e nenhuma configuração adicional seja necessária.

Configuração do isolamento de CSS


O isolamento de CSS permite a configuração de alguns cenários avançados, como
quando há dependências em ferramentas ou fluxos de trabalho existentes.

Personalizar o formato de identificador de escopo


Nesta seção, o espaço reservado {Pages|Views} é Pages para aplicativos Razor Pages ou
Views para aplicativos MVC.

Por padrão, os identificadores de escopo usam o formato b-{STRING} , em que o espaço


reservado {STRING} é uma cadeia de caracteres de dez caracteres gerada pela estrutura.
Para personalizar o formato do identificador de escopo, atualize o arquivo de projeto
para um padrão desejado:

XML

<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

No exemplo anterior, o CSS gerado para Index.cshtml.css altera o identificador de


escopo de b-{STRING} para custom-scope-identifier .

Use identificadores de escopo para obter a herança com arquivos CSS com escopo. No
exemplo de arquivo de projeto a seguir, um arquivo BaseView.cshtml.css contém
estilos comuns entre exibições. Um arquivo DerivedView.cshtml.css herda esses estilos.

XML

<ItemGroup>
<None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-
identifier" />
<None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-
scope-identifier" />
</ItemGroup>

Use o operador curinga ( * ) para compartilhar identificadores de escopo em vários


arquivos:

XML

<ItemGroup>
<None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

Alterar o caminho base para ativos da Web estáticos


O arquivo CSS com escopo é gerado na raiz do aplicativo. No arquivo de projeto, use a
propriedade StaticWebAssetBasePath para alterar o caminho padrão. O seguinte
exemplo coloca o arquivo CSS com escopo, e o restante dos ativos do aplicativo, no
caminho _content :

XML

<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

Desabilitar o agrupamento automático


Para recusar como a estrutura publica e carrega os arquivos com escopo no runtime,
use a propriedade DisableScopedCssBundling . Ao usar essa propriedade, outras
ferramentas ou processos são responsáveis por tirar os arquivos CSS isolados do
diretório obj e publicá-los e carregá-los em runtime:

XML

<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Suporte à RCL (biblioteca de classes) Razor


Quando uma RCL (biblioteca de classes) Razor fornece estilos isolados, o atributo
<link> da marca href aponta para {STATIC WEB ASSET BASE PATH}/{PACKAGE
ID}.bundle.scp.css , onde estão os espaços reservados:

{STATIC WEB ASSET BASE PATH} : o caminho de base do ativo da Web estático.
{PACKAGE ID} : o identificador de pacote da biblioteca. O identificador de pacote

usará como padrão o nome do assembly do projeto se não for especificado no


arquivo de projeto.

No exemplo a seguir:

O caminho de base do ativo da Web estático é _content/ClassLib .


O nome do assembly da biblioteca de classes é ClassLib .

Pages/Shared/_Layout.cshtml (Razor Pages) ou Views/Shared/_Layout.cshtml (MVC):


HTML

<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">

Para obter mais informações sobre RCLs, consulte os seguintes artigos:

Interface do usuário do Razor reutilizável em bibliotecas de classes com o ASP.NET


Core
Consumir componentes do Razor de uma Razor biblioteca de classes (RCL)

Para obter informações sobre o isolamento de CSS de Blazor, consulte Isolamento de


CSS do Blazor do ASP.NET Core.
Exibições parciais no ASP.NET Core
Artigo • 04/01/2023 • 9 minutos para o fim da leitura

Por Steve Smith , Maher JENDOUBI , Rick Anderson e Scott Sauber

Uma exibição parcial é um Razor arquivo de marcação ( .cshtml ) sem uma @page
diretiva que renderiza a saída HTML na saída renderizada de outro arquivo de marcação.

O termo exibição parcial é usado ao desenvolver um aplicativo MVC, em que os


arquivos de marcação são chamados de exibições ou um Razor aplicativo Pages, em que
os arquivos de marcação são chamados de páginas. Este tópico refere-se genericamente
a exibições de MVC e Razor páginas de páginas como arquivos de marcação.

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

Quando usar exibições parciais


Exibições parciais são uma maneira eficaz de:

Dividir arquivos de marcação grandes em componentes menores.

Aproveitar a vantagem de trabalhar com cada parte isolada em uma exibição


parcial em um arquivo de marcação grande e complexo, composto por diversas
partes lógicas. O código no arquivo de marcação é gerenciável porque a marcação
contém somente a estrutura de página geral e as referências a exibições parciais.

Reduzir a duplicação de conteúdo de marcação comum em arquivos de marcação.

Quando os mesmos elementos de marcação são usados nos arquivos de


marcação, uma exibição parcial remove a duplicação de conteúdo de marcação em
um arquivo de exibição parcial. Quando a marcação é alterada na exibição parcial,
ela atualiza a saída renderizada dos arquivos de marcação que usam a exibição
parcial.

As exibições parciais não podem ser usadas para manter os elementos de layout
comuns. Os elementos de layout comuns precisam ser especificados nos arquivos
_Layout.cshtml.

Não use uma exibição parcial em que a lógica de renderização complexa ou a execução
de código são necessárias para renderizar a marcação. Em vez de uma exibição parcial,
use um componente de exibição.
Declarar exibições parciais
Uma exibição parcial é um .cshtml arquivo de marcação sem uma @page diretiva
mantida na pasta Exibições (MVC) ou na pasta Páginas.Razor

No ASP.NET Core MVC, um ViewResult do controlador é capaz de retornar uma exibição


ou uma exibição parcial. Em Razor Páginas, é PageModel possível retornar uma exibição
parcial representada como um PartialViewResult objeto. A referência e a renderização de
exibições parciais são descritas na seção Referenciar uma exibição parcial.

Ao contrário da exibição do MVC ou da renderização de página, uma exibição parcial


não é executada _ViewStart.cshtml . Para obter mais informações
sobre _ViewStart.cshtml , consulte Layout no ASP.NET Core.

Nomes de arquivos de exibição parcial geralmente começam com um sublinhado ( _ ).


Essa convenção de nomenclatura não é obrigatória, mas ajuda a diferenciar visualmente
as exibições parciais das exibições e das páginas.

Referenciar uma exibição parcial

Usar uma exibição parcial em um Razor PageModel de


Páginas
Em ASP.NET Core 2.0 ou 2.1, o seguinte método de manipulador renderiza a exibição
parcial _AuthorPartialRP.cshtml para a resposta:

C#

public IActionResult OnGetPartial() =>


new PartialViewResult
{
ViewName = "_AuthorPartialRP",
ViewData = ViewData,
};

No ASP.NET Core 2.2 ou posterior, um método de manipulador pode, como alternativa,


chamar o método Partial para produzir um objeto PartialViewResult :

C#

public IActionResult OnGetPartial() =>


Partial("_AuthorPartialRP");
Usar uma exibição parcial em um arquivo de marcação
Há várias maneiras de referenciar uma exibição parcial em um arquivo de marcação. É
recomendável que os aplicativos usem uma das seguintes abordagens de renderização
assíncrona:

Auxiliar de marca parcial


Auxiliar de HTML assíncrono

Auxiliar de marca parcial


O Auxiliar de Marca Parcial exige o ASP.NET Core 2.1 ou posterior.

O Auxiliar de Marca Parcial renderiza o conteúdo de forma assíncrona e usa uma sintaxe
semelhante a HTML:

CSHTML

<partial name="_PartialName" />

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:

CSHTML

<partial name="_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:

Razor Páginas

CSHTML

<partial name="~/Pages/Folder/_PartialName.cshtml" />


<partial name="/Pages/Folder/_PartialName.cshtml" />

MVC

CSHTML

<partial name="~/Views/Folder/_PartialName.cshtml" />


<partial name="/Views/Folder/_PartialName.cshtml" />
O exemplo a seguir faz referência a uma exibição parcial com um caminho relativo:

CSHTML

<partial name="../Account/_PartialName.cshtml" />

Para obter mais informações, consulte o 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 @ :

CSHTML

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

CSHTML

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

Razor Páginas

CSHTML

@await Html.PartialAsync("~/Pages/Folder/_PartialName.cshtml")
@await Html.PartialAsync("/Pages/Folder/_PartialName.cshtml")

MVC

CSHTML

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

CSHTML

@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 um resultado, ele deve ser
chamado dentro de um Razor bloco de código:

CSHTML

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

) Importante

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.

Descoberta de exibição parcial


Quando uma exibição parcial é referenciada pelo nome sem uma extensão de arquivo,
os seguintes locais são pesquisados na ordem indicada:

Razor Páginas

1. Pasta da página em execução no momento


2. Grafo do diretório acima da pasta da página
3. /Shared
4. /Pages/Shared
5. /Views/Shared

MVC

1. /Areas/<Area-Name>/Views/<Controller-Name>
2. /Areas/<Area-Name>/Views/Shared
3. /Views/Shared
4. /Pages/Shared

As convenções a seguir se aplicam à descoberta de exibição parcial:

Diferentes exibições parciais com o mesmo nome de arquivo são permitidos


quando as exibições parciais estão em pastas diferentes.
Ao referenciar uma exibição parcial pelo nome sem uma extensão de arquivo e a
exibição parcial está presente na pasta do chamador e na pasta Compartilhada, a
exibição parcial na pasta do chamador fornece a exibição parcial. Se a exibição
parcial não existir na pasta do chamador, ela será fornecida pela pasta
Compartilhada. Exibições parciais na pasta Compartilhada são chamadas de
exibições parciais compartilhadas ou exibições parciais padrão.
Exibições parciais podem ser encadeadas— uma exibição parcial pode chamar
outra exibição parcial se uma referência circular não for formada pelas chamadas.
Caminhos relativos sempre são relativos ao arquivo atual, não à raiz ou ao pai do
arquivo.

7 Observação
Um Razor section definido em uma exibição parcial é invisível para arquivos de
marcação pai. A section só é visível para a exibição parcial na qual ela está
definida.

Acessar dados de exibições parciais


Quando uma exibição parcial é instanciada, ela recebe uma cópia do dicionário
ViewData do pai. As atualizações feitas nos dados dentro da exibição parcial não são
persistidas na exibição pai. Alterações a ViewData em uma exibição parcial são perdidas
quando a exibição parcial retorna.

O exemplo a seguir demonstra como passar uma instância para ViewDataDictionary


uma exibição parcial:

CSHTML

@await Html.PartialAsync("_PartialName", customViewData)

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

CSHTML

@await Html.PartialAsync("_PartialName", model)

Razor Páginas

A marcação a seguir no aplicativo de exemplo é da Pages/ArticlesRP/ReadRP.cshtml


página. 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.

CSHTML

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

foreach (var section in Model.Article.Sections)


{
await Html.PartialAsync("_ArticleSectionRP",
section,
new ViewDataDictionary(ViewData)
{
{ "index", index }
});

index++;
}
}

Pages/Shared/_AuthorPartialRP.cshtml é a primeira exibição parcial referenciada pelo


ReadRP.cshtml arquivo de marcação:

CSHTML

@model string
<div>
<h3>@Model</h3>
This partial view from /Pages/Shared/_AuthorPartialRP.cshtml.
</div>

Pages/ArticlesRP/_ArticleSectionRP.cshtml é a segunda exibição parcial referenciada


pelo ReadRP.cshtml arquivo de marcação:

CSHTML

@using PartialViewsSample.ViewModels
@model ArticleSection

<h3>@Model.Title Index: @ViewData["index"]</h3>


<div>
@Model.Content
</div>

MVC

A marcação a seguir no aplicativo de exemplo mostra o modo de 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.

CSHTML

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

foreach (var section in Model.Sections)


{
@(await Html.PartialAsync("_ArticleSection",
section,
new ViewDataDictionary(ViewData)
{
{ "index", index }
}))

index++;
}
}

Views/Shared/_AuthorPartial.cshtml é a primeira exibição parcial referenciada pelo


Read.cshtml arquivo de marcação:

CSHTML

@model string
<div>
<h3>@Model</h3>
This partial view from /Views/Shared/_AuthorPartial.cshtml.
</div>

Views/Articles/_ArticleSection.cshtml é a segunda exibição parcial referenciada pelo

Read.cshtml arquivo de marcação:

CSHTML

@using PartialViewsSample.ViewModels
@model ArticleSection
<h3>@Model.Title Index: @ViewData["index"]</h3>
<div>
@Model.Content
</div>

No runtime, as parciais são renderizadas na saída renderizada do arquivo de marcação


pai, que é renderizada dentro do compartilhado _Layout.cshtml . 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

A segunda exibição parcial renderiza as seções do artigo:

Índice da seção um: 0

Pontuação de quatro e há sete anos...

Índice da seção dois: 1

Agora estamos envolvidos em uma grande guerra civil, testando...

Índice da seção três: 2

Mas, em um sentido mais amplo, não podemos dedicar...

Recursos adicionais
Razorreferência de sintaxe 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
Tratar solicitações com controladores no
ASP.NET Core MVC
Artigo • 04/01/2023 • 5 minutos para o fim da leitura

Por Steve Smith e Scott Addie

Controladores, ações e resultados da ação são uma parte fundamental de como os


desenvolvedores criam aplicativos usando o ASP.NET Core MVC.

O que é um controlador?
Um controlador é usado para definir e agrupar um conjunto de ações. Uma ação (ou
método de ação) é um método em um controlador que manipula solicitações. Os
controladores agrupam ações semelhantes de forma lógica. Essa agregação de ações
permite que conjuntos de regras comuns, como roteamento, cache e autorização, sejam
aplicados em conjunto. As solicitações são mapeadas para ações por meio de
roteamento.

Por convenção, as classes do controlador:

Resida na pasta Controladores de nível raiz do projeto.


Herdar de Microsoft.AspNetCore.Mvc.Controller .

Um controlador é uma classe instanciável, geralmente pública, na qual pelo menos uma
das seguintes condições é verdadeira:

O nome da classe é sufixo com Controller .


A classe herda de uma classe cujo nome é sufixo com Controller .
O [Controller] atributo é aplicado à classe.

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.

Como definir ações


Métodos públicos em um controlador, exceto aqueles com o [NonAction] atributo, são
ações. Parâmetros em ações são associados aos dados de solicitação e validados
usando o model binding. A validação de modelo ocorre em tudo o que é associado ao
modelo. O valor da propriedade ModelState.IsValid indica se o model binding 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, Controllerembora isso não seja necessário. A
derivação de Controller fornece acesso a três categorias de métodos auxiliares:

1. Métodos que resultam em um corpo de resposta vazio

Nenhum cabeçalho de resposta HTTP Content-Type é incluído, pois o corpo da resposta


não tem nenhum conteúdo a ser descrito.

Há dois tipos de resultado nessa categoria: Redirecionamento e Código de Status HTTP.


Código de status HTTP

Esse tipo retorna um código de status HTTP. Alguns métodos auxiliares desse tipo
são BadRequest , NotFound e Ok . Por exemplo, return BadRequest(); produz um
código de status 400 quando executado. Quando métodos como BadRequest ,
NotFound e Ok estão sobrecarregados, eles deixam de se qualificar como

respondentes do Código de Status HTTP, pois a negociação de conteúdo está em


andamento.

Redirecionar

Esse tipo retorna um redirecionamento para uma ação ou um destino (usando


Redirect , LocalRedirect , RedirectToAction ou RedirectToRoute ). Por exemplo,

return RedirectToAction("Complete", new {id = 123}); redireciona para

Complete , passando um objeto anônimo.

O tipo de resultado do Redirecionamento é diferente do tipo do Código de Status


HTTP, principalmente na adição de um cabeçalho de resposta HTTP Location .

2. Métodos que resultam em um corpo de resposta não vazio com


um tipo de conteúdo predefinido
A maioria dos métodos auxiliares desta categoria inclui uma propriedade ContentType ,
permitindo que você defina o cabeçalho de resposta Content-Type para descrever o
corpo da resposta.

Há dois tipos de resultado nessa categoria: Exibição e Resposta Formatada.

Exibir

Esse tipo retorna uma exibição que usa um modelo para renderizar HTML. Por
exemplo, return View(customer); passa um modelo para a exibição para
associação de dados.

Resposta Formatada

Esse tipo retorna JSON ou um formato de troca de dados semelhante para


representar um objeto de maneira específica. Por exemplo, return
Json(customer); serializa o objeto fornecido no JSformato ON.

Outros métodos comuns desse tipo incluem File e PhysicalFile . Por exemplo,
return PhysicalFile(customerFilePath, "text/xml"); retorna PhysicalFileResult.
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 se aplica sempre que uma ação retorna um ObjectResult tipo 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. A utilização de filtros em
interesses paralelos pode reduzir a duplicação.

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
Artigo • 04/01/2023 • 79 minutos para o fim da leitura

Por Ryan Nowak , Kirk Larkin e Rick Anderson

ASP.NET Core controladores usam o middleware de roteamento para corresponder às


URLs das solicitações de entrada e mapeá-las para ações. Modelos de rota:

São definidos na inicialização ou Program.cs em atributos.


Descreva como os caminhos de URL são correspondidos às ações.
São usados para gerar URLs para links. Os links gerados normalmente são
retornados em respostas.

As ações são roteados convencionalmente ou roteados por atributo. Colocar uma rota
no controlador ou na ação o torna roteado por atributo. Para obter mais informações,
consulte Roteamento misto.

Este documento:

Explica as interações entre o MVC e o roteamento:


Como aplicativos MVC típicos usam recursos de roteamento.
Cobre ambos:
Roteamento convencional normalmente usado com controladores e
exibições.
Roteamento de atributo usado com REST APIs. Se você estiver interessado
principalmente no roteamento de REST APIs, vá para a seção Roteamento de
atributo para REST APIs .
Consulte Roteamento para obter detalhes avançados de roteamento.
Refere-se ao sistema de roteamento padrão chamado roteamento de ponto de
extremidade. É possível usar controladores com a versão anterior do roteamento
para fins de compatibilidade. Consulte o guia de migração 2.2-3.0 para obter
instruções.

Configurar rota convencional


O modelo ASP.NET Core MVC gera um código de roteamento convencional semelhante
ao seguinte:

C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

MapControllerRoute é usado para criar uma única rota. A rota única é chamada default
de rota. A maioria dos aplicativos com controladores e exibições usa um modelo de rota
semelhante à default rota. REST As APIs devem usar o roteamento de atributo.

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

O modelo de "{controller=Home}/{action=Index}/{id?}" rota:

Corresponde a um caminho de URL como /Products/Details/5

Extrai os valores { controller = Products, action = Details, id = 5 } de rota


tokenizando o caminho. A extração de valores de rota resultará em uma
correspondência se o aplicativo tiver um controlador nomeado
ProductsController e uma Details ação:

C#

public class ProductsController : Controller


{
public IActionResult Details(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

MyDisplayRouteInfo é fornecido pelo pacote NuGet


Rick.Docs.Samples.RouteInfo e exibe informações de rota.

/Products/Details/5 o modelo associa o valor de id = 5 definir o id parâmetro

como 5 . Consulte a Associação de Modelos para obter mais detalhes.

{controller=Home} define Home como o padrão controller .

{action=Index} define Index como o padrão action .

O ? caractere em {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.

Corresponde ao caminho / da URL.

Produz os valores { controller = Home, action = Index } de rota.

Os valores para controller e action fazer uso dos valores padrão. id não produz um
valor, pois não há nenhum segmento correspondente no caminho da URL. / só
corresponderá se houver uma ação e Index : HomeController

C#

public class HomeController : Controller


{
public IActionResult Index() { ... }
}

Usando a definição do controlador anterior e o modelo de rota, a HomeController.Index


ação é executada para os seguintes caminhos de URL:

/Home/Index/17
/Home/Index

/Home
/
O caminho / da URL usa os controladores e Index a ação padrão Home do modelo de
rota. O caminho /Home da URL usa a ação padrão Index do modelo de rota.

O método de conveniência MapDefaultControllerRoute:

C#

app.MapDefaultControllerRoute();

Substitui:

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

) Importante

O roteamento é configurado usando o middleware e UseEndpoints o UseRouting


middleware. Para usar controladores:

Chamar MapControllers para mapear controladores roteados de atributo .


Chamar MapControllerRoute ou MapAreaControllerRoutemapear
controladores roteados convencionalmente e controladores roteados de
atributo .

Normalmente, os aplicativos não precisam chamar UseRouting ou UseEndpoints .


WebApplicationBuilder configura um pipeline de middleware que encapsula o
middleware adicionado Program.cs com UseRouting e UseEndpoints . Para obter
mais informações, consulte Roteamento em ASP.NET Core.

Roteamento convencional
O roteamento convencional é usado com controladores e exibições. A rota default :

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
O anterior é um exemplo de uma rota convencional. Ele é chamado de roteamento
convencional porque estabelece uma convenção para caminhos de URL:

O primeiro segmento de caminho, {controller=Home} mapeia para o nome do


controlador.
O segundo segmento, {action=Index} mapeia para o nome da ação .
O terceiro segmento é {id?} usado para um opcional id . O ? in {id?} torna-o
opcional. id é usado para mapear para uma entidade de modelo.

Usando essa default rota, o caminho da URL:

/Products/List mapeia para a ação ProductsController.List .


/Blog/Article/17 mapeia e BlogController.Article , normalmente, o modelo

associa o id parâmetro a 17.

Este mapeamento:

Baseia-se apenas no controlador e nos nomes de ação.


Não se baseia em namespaces, locais de arquivo de origem ou parâmetros de
método.

Usar o roteamento convencional com a rota padrão permite criar o aplicativo sem
precisar criar um novo padrão de URL para cada ação. Para um aplicativo com ações de
estilo CRUD , ter consistência para as URLs entre controladores:

Ajuda a simplificar o código.


Torna a interface do usuário mais previsível.

2 Aviso

O id código anterior é definido como opcional pelo modelo de rota. As ações


podem ser executadas sem a ID opcional fornecida como parte da URL.
Geralmente, quando id é omitido da URL:

id é definido 0 como por associação de modelo.

Nenhuma entidade é encontrada na correspondência id == 0 do banco de


dados.

O roteamento de atributo fornece controle refinado para tornar a ID necessária


para algumas ações e não para outras. Por convenção, a documentação inclui
parâmetros opcionais, como id quando eles provavelmente aparecerão no uso
correto.
A maioria dos aplicativos deve escolher um esquema de roteamento básico e descritivo
para que as URLs sejam legíveis e significativas. A rota convencional padrão
{controller=Home}/{action=Index}/{id?} :

Dá suporte a um esquema de roteamento básico e descritivo.


É um ponto de partida útil para aplicativos baseados em interface do usuário.
É o único modelo de rota necessário para muitos aplicativos de interface do
usuário da Web. Para aplicativos de interface do usuário web maiores, outra rota
usando Áreas é frequentemente tudo o que é necessário.

MapControllerRoute e MapAreaRoute :

Atribua automaticamente um valor de pedido aos pontos de extremidade com


base na ordem em que são invocados.

Roteamento de ponto de extremidade em ASP.NET Core:

Não tem um conceito de rotas.


Não fornece garantias de ordenação para a execução da extensibilidade, todos os
pontos de extremidade são processados de uma só vez.

Habilite o Log para ver como as implementações de roteamento internas, como Route,
correspondem às solicitações.

O roteamento de atributo é explicado posteriormente neste documento.

Várias rotas convencionais


Várias rotas convencionais podem ser configuradas adicionando mais chamadas e
MapControllerRouteMapAreaControllerRoute. Isso permite definir várias convenções ou
adicionar rotas convencionais dedicadas a uma ação específica, como:

C#

app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

A blog rota no código anterior é uma rota convencional dedicada. É chamada de rota
convencional dedicada porque:

Ele usa roteamento convencional.


Ele é dedicado a uma ação específica.

Porque controller e action não aparecem no modelo "blog/{*article}" de rota


como parâmetros:

Eles só podem ter os valores { controller = "Blog", action = "Article" } padrão.


Essa rota sempre é mapeada para a ação BlogController.Article .

/Blog , /Blog/Article e /Blog/{any-string} são os únicos caminhos de URL que

correspondem à rota do blog.

O exemplo anterior:

blog A rota tem uma prioridade maior para correspondências do que a default
rota porque ela é adicionada primeiro.
É um exemplo de roteamento de estilo Slug em que é comum ter um nome de
artigo como parte da URL.

2 Aviso

Em ASP.NET Core, o roteamento não:

Defina um conceito chamado rota. UseRouting adiciona a correspondência de


rotas ao pipeline do middleware. O UseRouting middleware examina o
conjunto de pontos de extremidade definidos no aplicativo e seleciona a
melhor correspondência de ponto de extremidade com base na solicitação.
Forneça garantias sobre a ordem de execução de extensibilidade como
IRouteConstraint ou IActionConstraint.

Consulte Roteamento para obter material de referência no roteamento.

Ordem de roteamento convencional


O roteamento convencional corresponde apenas a uma combinação de ação e
controlador definida pelo aplicativo. Isso se destina a simplificar casos em que as rotas
convencionais se sobrepõem. Adicionando rotas usando MapControllerRoute,
MapDefaultControllerRoutee MapAreaControllerRoute atribua automaticamente um
valor de pedido aos pontos de extremidade com base na ordem em que são invocados.
As correspondências de uma rota que aparece anteriormente têm uma prioridade mais
alta. O roteamento convencional é dependente da ordem. Em geral, as rotas com áreas
devem ser colocadas anteriormente, pois são mais específicas do que as rotas sem uma
área. Rotas convencionais dedicadas com parâmetros de rota catch-all como {*article}
podem tornar uma rota muito gananciosa, o que significa que ela corresponde às URLs
que você pretendia que fossem correspondidas por outras rotas. Coloque as rotas
gananciosas mais tarde na tabela de rotas para evitar correspondências gananciosas.

Resolvendo ações ambíguas


Quando dois pontos de extremidade correspondem ao roteamento, o roteamento deve
fazer um dos seguintes procedimentos:

Escolha o melhor candidato.


Gera uma exceção.

Por exemplo:

C#

public class Products33Controller : Controller


{
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[HttpPost]
public IActionResult Edit(int id, Product product)
{
return ControllerContext.MyDisplayRouteInfo(id, product.name);
}
}

O controlador anterior define duas ações que correspondem:

O caminho da URL /Products33/Edit/17


Rotear dados { controller = Products33, action = Edit, id = 17 } .

Esse é um padrão típico para controladores MVC:

Edit(int) exibe um formulário para editar um produto.

Edit(int, Product) processa o formulário postado.

Para resolver a rota correta:

Edit(int, Product) é selecionado quando a solicitação é um HTTP POST .

Edit(int) é selecionado quando o verbo HTTP é qualquer outra coisa. Edit(int)


geralmente é chamado via GET .
O HttpPostAttribute, [HttpPost] é fornecido para roteamento para que ele possa
escolher com base no método HTTP da solicitação. O HttpPostAttribute faz Edit(int,
Product) uma correspondência melhor do que Edit(int) .

É importante entender a função de atributos como HttpPostAttribute . Atributos


semelhantes são definidos para outros verbos HTTP. No roteamento convencional, é
comum que as ações usem o mesmo nome de ação quando fazem parte de um
formulário de exibição, enviar fluxo de trabalho de formulário. Por exemplo, consulte
Examinar os dois métodos de ação Editar.

Se o roteamento não puder escolher um melhor candidato, um


AmbiguousMatchException será lançado, listando os vários pontos de extremidade
correspondentes.

Nomes de rotas convencionais


As cadeias de caracteres "blog" e "default" , nos exemplos a seguir, são nomes de rota
convencionais:

C#

app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Os nomes de rota dão à rota um nome lógico. A rota nomeada pode ser usada para
geração de URL. O uso de uma rota nomeada simplifica a criação de URL quando a
ordenação de rotas pode complicar a geração de URL. Os nomes de rota devem ter um
aplicativo exclusivo.

Nomes de rota:

Não tenha nenhum impacto na correspondência de URL ou no tratamento de


solicitações.
São usados somente para geração de URL.

O conceito de nome de rota é representado no roteamento como


IEndpointNameMetadata. O nome da rota dos termos e o nome do ponto de
extremidade:

São intercambiáveis.
Qual deles é usado na documentação e no código depende da API que está sendo
descrita.

Roteamento de atributo para REST APIs


REST As APIs devem usar o roteamento de atributo para modelar a funcionalidade do
aplicativo como um conjunto de recursos em que as operações são representadas por
verbos HTTP.

O roteamento de atributo usa um conjunto de atributos para mapear ações diretamente


para modelos de rota. O código a seguir é típico de uma REST API e é usado no
próximo exemplo:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

No código anterior, MapControllers é chamado para mapear controladores roteados de


atributo.

No exemplo a seguir:

HomeController corresponde a um conjunto de URLs semelhante ao que a rota


{controller=Home}/{action=Index}/{id?} convencional padrão corresponde.

C#

public class HomeController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

A HomeController.Index ação é executada para qualquer um dos caminhos / de URL,


/Home /Home/Index ou /Home/Index/3 .

Este exemplo destaca uma diferença de programação chave entre o roteamento de


atributo e o roteamento convencional. O roteamento de atributo requer mais entrada
para especificar uma rota. A rota padrão convencional manipula rotas de forma mais
sucinta. No entanto, o roteamento de atributo permite e requer um controle preciso de
quais modelos de rota se aplicam a cada ação.

Com o roteamento de atributo, os nomes do controlador e da ação não desempenham


nenhum papel no qual a ação é correspondida, a menos que a substituição de token
seja usada. O exemplo a seguir corresponde às mesmas URLs do exemplo anterior:

C#

public class MyDemoController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

O código a seguir usa a substituição de token e action controller :

C#
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("[controller]/[action]")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}

[Route("[controller]/[action]")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

O código a seguir se aplica [Route("[controller]/[action]")] ao controlador:

C#

[Route("[controller]/[action]")]
public class HomeController : Controller
{
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}

public IActionResult About()


{
return ControllerContext.MyDisplayRouteInfo();
}
}

No código anterior, os Index modelos de método devem pré-acrescentar / ou ~/ aos


modelos de rota. Modelos de rota aplicados a uma ação, que começam com / ou ~/ ,
não são combinados com modelos de rota aplicados ao controlador.

Consulte a precedência do modelo de rota para obter informações sobre a seleção de


modelo de rota.

Nomes reservados de roteamento


As seguintes palavras-chave são nomes de parâmetro de rota reservados ao usar
Controladores ou Razor Páginas:

action

area
controller

handler

page

Usar page como um parâmetro de rota com roteamento de atributo é um erro comum.
Fazer isso resulta em um comportamento inconsistente e confuso com a geração de
URL.

C#

public class MyDemo2Controller : Controller


{
[Route("/articles/{page}")]
public IActionResult ListArticles(int page)
{
return ControllerContext.MyDisplayRouteInfo(page);
}
}

Os nomes de parâmetros especiais são usados pela geração de URL para determinar se
uma operação de geração de URL se refere a uma Razor página ou a um controlador.

As seguintes palavras-chave são reservadas no contexto de um Razor modo de


exibição ou de uma Razor página:

page

using

namespace

inject

section

inherits

model

addTagHelper
removeTagHelper

Essas palavras-chave não devem ser usadas para gerações de vínculo, parâmetros
associados ao modelo ou propriedades de nível superior.

Modelos de verbo HTTP


ASP.NET Core tem os seguintes modelos de verbo HTTP:

[HttpGet]
[HttpPost]
[HttpPut]
[HttpDelete]
[HttpHead]
[HttpPatch]

Modelos de rota
ASP.NET Core tem os seguintes modelos de rota:

Todos os modelos de verbo HTTP são modelos de rota.


[Rota]

Roteamento de atributos com atributos de verbo Http


Considere o seguinte controlador:

C#

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("{id}")] // GET /api/test2/xyz


public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[HttpGet("int/{id:int}")] // GET /api/test2/int/3


public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[HttpGet("int2/{id}")] // GET /api/test2/int2/3


public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

No código anterior:

Cada ação contém o [HttpGet] atributo, que restringe a correspondência somente


com solicitações HTTP GET.
A GetProduct ação inclui o "{id}" modelo, portanto id , é acrescentada ao
"api/[controller]" modelo no controlador. O modelo de métodos é

"api/[controller]/"{id}"" . Portanto, essa ação corresponde apenas às


solicitações GET para o formulário /api/test2/xyz , /api/test2/123 /api/test2/{any
string} etc.

C#

[HttpGet("{id}")] // GET /api/test2/xyz


public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

A GetIntProduct ação contém o "int/{id:int}") modelo. A :int parte do


modelo restringe os valores de id rota para cadeias de caracteres que podem ser
convertidas em um inteiro. Uma solicitação GET para /api/test2/int/abc :
Não corresponde a essa ação.
Retorna um erro 404 Not Found .

C#

[HttpGet("int/{id:int}")] // GET /api/test2/int/3


public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

A GetInt2Product ação contém {id} no modelo, mas não restringe id a valores


que podem ser convertidos em um inteiro. Uma solicitação GET para
/api/test2/int2/abc :
Corresponde a essa rota.
A associação de modelo falha ao converter abc em um inteiro. O id parâmetro
do método é inteiro.
Retorna uma Solicitação 400 Incorreta porque a associação de modelo não
conseguiu converter abc em um inteiro.

C#

[HttpGet("int2/{id}")] // GET /api/test2/int2/3


public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

O roteamento de atributos pode usar HttpMethodAttribute atributos como


HttpPostAttribute, HttpPutAttributee HttpDeleteAttribute. Todos os atributos de verbo
HTTP aceitam um modelo de rota. O exemplo a seguir mostra duas ações que
correspondem ao mesmo modelo de rota:

C#

[ApiController]
public class MyProductsController : ControllerBase
{
[HttpGet("/products3")]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpPost("/products3")]
public IActionResult CreateProduct(MyProduct myProduct)
{
return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
}
}

Usando o caminho /products3 da URL:

A MyProductsController.ListProducts ação é executada quando o verbo HTTP é


GET .

A MyProductsController.CreateProduct ação é executada quando o verbo HTTP é


POST .

Ao criar uma REST API, é raro que você precise usar [Route(...)] em um método de
ação porque a ação aceita todos os métodos HTTP. É melhor usar o atributo de verbo
HTTP mais específico para ser preciso sobre o suporte à API. Espera-se que os clientes
de REST APIs saibam quais caminhos e verbos HTTP mapeiam para operações lógicas
específicas.

REST As APIs devem usar o roteamento de atributo para modelar a funcionalidade do


aplicativo como um conjunto de recursos em que as operações são representadas por
verbos HTTP. Isso significa que muitas operações, por exemplo, GET e POST no mesmo
recurso lógico usam a mesma URL. O roteamento de atributo fornece um nível de
controle necessário para projetar cuidadosamente o layout de ponto de extremidade
público de uma API.

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. No exemplo
a seguir, id é necessário como parte do caminho da URL:

C#

[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

A Products2ApiController.GetProduct(int) ação:

É executado com o caminho da URL, como /products2/3


Não é executado com o caminho /products2 da URL.

O atributo [Consome] permite que uma ação limite os tipos de conteúdo de solicitação
com suporte. Para obter mais informações, consulte Definir tipos de conteúdo de
solicitação com suporte com o atributo Consumas.

Consulte Roteamento para obter uma descrição completa de modelos de rota e as


opções relacionadas.

Para obter mais informações sobre [ApiController] , consulte o atributo ApiController.

Nome da rota
O código a seguir define um nome de rota de Products_List :
C#

[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Nomes de rota podem ser usados para gerar uma URL com base em uma rota
específica. Nomes de rota:

Não tenha nenhum impacto no comportamento de correspondência de URL do


roteamento.
São usados apenas para geração de URL.

Nomes de rotas devem ser exclusivos no nível do aplicativo.

Contraste o código anterior com a rota padrão convencional, que define o id


parâmetro como opcional ( {id?} ). A capacidade de especificar com precisão as APIs
tem vantagens, como permitir /products e /products/5 ser enviado para diferentes
ações.

Combinando rotas de atributo


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.

C#

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
[HttpGet]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

No exemplo anterior:

O caminho /products da URL pode corresponder ProductsApi.ListProducts


O caminho /products/5 da URL pode corresponder ProductsApi.GetProduct(int) .

Ambas as ações correspondem somente a HTTP GET porque estão marcadas com o
[HttpGet] atributo.

Modelos de rota aplicados a uma ação, que começam com / ou ~/ , não são
combinados com modelos de rota aplicados ao controlador. O exemplo a seguir
corresponde a um conjunto de caminhos de URL semelhantes à rota padrão.

C#

[Route("Home")]
public class HomeController : Controller
{
[Route("")]
[Route("Index")]
[Route("/")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}

[Route("About")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

A tabela a seguir explica os [Route] atributos no código anterior:

Atributo Combina com [Route("Home")] Define o modelo de rota

[Route("")] Sim "Home"

[Route("Index")] Sim "Home/Index"

[Route("/")] Não ""

[Route("About")] Sim "Home/About"


Ordem de rota de atributo
O roteamento cria uma árvore e corresponde a todos os pontos de extremidade
simultaneamente:

As entradas de rota se comportam como se colocadas em uma ordenação ideal.


As rotas mais específicas têm a chance de serem executadas antes das rotas mais
gerais.

Por exemplo, uma rota de atributo como blog/search/{topic} é mais específica do que
uma rota de atributo como blog/{*article} . A blog/search/{topic} rota tem
prioridade mais alta, por padrão, porque é mais específica. Usando o roteamento
convencional, o desenvolvedor é responsável por colocar rotas na ordem desejada.

As rotas de atributo podem configurar um pedido usando a Order propriedade. Todos


os atributos de rota fornecidos pela estrutura incluem Order . As rotas são processadas
segundo uma classificação crescente da propriedade Order . A ordem padrão é 0 .
Definir uma rota usando Order = -1 execuções antes de rotas que não definem um
pedido. Definir uma rota usando Order = 1 execuções após a ordenação de rota
padrão.

Evite dependendo Order . Se o espaço de URL de um aplicativo exigir valores de pedido


explícitos para rotear corretamente, provavelmente também será confuso para os
clientes. Em geral, o roteamento de atributo seleciona a rota correta com
correspondência de URL. Se a ordem padrão usada para a geração de URL não estiver
funcionando, usar um nome de rota como uma substituição geralmente será mais
simples do que aplicar a Order propriedade.

Considere os dois controladores a seguir que definem a correspondência /home de rotas:

C#

public class HomeController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

C#

public class MyDemoController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

A solicitação /home com o código anterior gera uma exceção semelhante à seguinte:

text

AmbiguousMatchException: The request matched multiple endpoints. Matches:

WebMvcRouting.Controllers.HomeController.Index
WebMvcRouting.Controllers.MyDemoController.MyIndex

Adicionar Order a um dos atributos de rota resolve a ambiguidade:

C#

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
return ControllerContext.MyDisplayRouteInfo();
}
Com o código anterior, /home executa o HomeController.Index ponto de extremidade.
Para acessar a solicitação MyDemoController.MyIndex /home/MyIndex . Observação:

O código anterior é um exemplo ou design de roteamento ruim. Foi usado para


ilustrar a Order propriedade.
A Order propriedade resolve apenas a ambiguidade, esse modelo não pode ser
correspondido. Seria melhor remover o [Route("Home")] modelo.

Consulte Razor as convenções de rota e aplicativo do Pages: rotear a ordem para obter
informações sobre o pedido de rota com Razor Páginas.

Em alguns casos, um erro HTTP 500 é retornado com rotas ambíguas. Use o log para ver
quais pontos de extremidade causaram o AmbiguousMatchException .

Substituição de token em modelos de rota


[controlador], [ação], [área]
Para conveniência, as rotas de atributo dão suporte à substituição de token colocando
um token entre colchetes ( [ , ] ). Os tokens [action] , [area] e [controller] são
substituídos pelos valores do nome da ação, nome da área e nome do controlador da
ação em que a rota é definida:

C#

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

No código anterior:

C#
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}

Corresponde /Products0/List

C#

[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

Corresponde /Products0/Edit/{id}

A substituição de token ocorre como a última etapa da criação das rotas de atributo. O
exemplo anterior se comporta da mesma forma que o seguinte código:

C#

public class Products20Controller : Controller


{
[HttpGet("[controller]/[action]")] // Matches '/Products20/List'
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("[controller]/[action]/{id}")] // Matches
'/Products20/Edit/{id}'
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Se você estiver lendo isso em um idioma diferente do inglês, informe-nos neste


problema de discussão do GitHub se quiser ver os comentários de código em seu
idioma nativo.

Rotas de atributo também podem ser combinadas com herança. Isso é poderoso
combinado com a substituição de token. 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:
C#

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller


{
[HttpGet] // /api/products11/list
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("{id}")] // /api/products11/edit/3
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Para corresponder ao delimitador [ de substituição de token literal ou ] , escape-o


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 personalizado SlugifyParameterTransformer altera o valor
da SubscriptionManagement rota para subscription-management :

C#

using System.Text.RegularExpressions;

public class SlugifyParameterTransformer : IOutboundParameterTransformer


{
public string? TransformOutbound(object? value)
{
if (value == null) { return null; }

return Regex.Replace(value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,

TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}

O RouteTokenTransformerConvention é uma convenção de modelo de aplicativo que:

Aplica um transformador de parâmetro a todas as rotas de atributo em um


aplicativo.
Personaliza os valores de token de rota de atributo conforme eles são substituídos.

C#

public class SubscriptionManagementController : Controller


{
[HttpGet("[controller]/[action]")]
public IActionResult ListAll()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

O método anterior ListAll corresponde /subscription-management/list-all .

A RouteTokenTransformerConvention opção é registrada como uma opção:

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(new RouteTokenTransformerConvention(
new SlugifyParameterTransformer()));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();
app.UseAuthorization();

app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Consulte documentos da Web do MDN no Slug para obter a definição de Slug.

2 Aviso

Ao usar System.Text.RegularExpressions para processar entradas não confiáveis,


passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada para
RegularExpressions causar um ataque de negação de serviço . ASP.NET Core
APIs de estrutura que usam RegularExpressions passar um tempo limite.

Várias rotas de atributo


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:

C#

[Route("[controller]")]
public class Products13Controller : Controller
{
[Route("")] // Matches 'Products13'
[Route("Index")] // Matches 'Products13/Index'
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}

Colocar vários atributos de rota no controlador significa que cada um combina com
cada um dos atributos de rota nos métodos de ação:

C#

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
[HttpPost("Buy")] // Matches 'Products6/Buy' and 'Store/Buy'
[HttpPost("Checkout")] // Matches 'Products6/Checkout' and
'Store/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

Todas as restrições de rota de verbo HTTP são implementadas IActionConstraint .

Quando vários atributos de rota que implementam IActionConstraint são colocados em


uma ação:

Cada restrição de ação combina com o modelo de rota aplicado ao controlador.

C#

[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
[HttpPut("Buy")] // Matches PUT 'api/Products7/Buy'
[HttpPost("Checkout")] // Matches POST 'api/Products7/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

Usar várias rotas em ações pode parecer útil e poderoso, é melhor manter o espaço de
URL do aplicativo básico e bem definido. Use várias rotas em ações somente quando
necessário, por exemplo, para dar suporte a clientes existentes.

Especificando parâmetros opcionais, valores padrão e


restrições da rota de atributo
Rotas de atributo dão suporte à mesma sintaxe embutida que as rotas convencionais
para especificar parâmetros opcionais, valores padrão e restrições.

C#

public class Products14Controller : Controller


{
[HttpPost("product14/{id:int}")]
public IActionResult ShowProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
No código anterior, [HttpPost("product14/{id:int}")] aplica uma restrição de rota. A
Products14Controller.ShowProduct ação é correspondida somente por caminhos de URL
como /product14/3 . A parte do modelo de {id:int} rota restringe esse segmento a
apenas inteiros.

Consulte Referência de modelo de rota para obter uma descrição detalhada da sintaxe
do modelo de rota.

Atributos de rota personalizados usando


IRouteTemplateProvider
Todos os atributos de rota são implementadosIRouteTemplateProvider. O runtime
ASP.NET Core:

Procura atributos em classes de controlador e métodos de ação quando o


aplicativo é iniciado.
Usa os atributos que implementam IRouteTemplateProvider para criar o conjunto
inicial de rotas.

Implementar IRouteTemplateProvider para definir atributos de rota personalizados.


Cada IRouteTemplateProvider permite definir uma única rota com um nome, uma
ordem e um modelo de rota personalizado:

C#

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider


{
public string Template => "api/[controller]";
public int? Order => 2;
public string Name { get; set; } = string.Empty;
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
// GET /api/MyTestApi
[HttpGet]
public IActionResult Get()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

O método anterior Get retorna Order = 2, Template = api/MyTestApi .


Usar o modelo de aplicativo para personalizar rotas de
atributo
O modelo de aplicativo:

É um modelo de objeto criado na inicialização em Program.cs .


Contém todos os metadados usados por ASP.NET Core para rotear e executar as
ações em um aplicativo.

O modelo de aplicativo inclui todos os dados coletados dos atributos de rota. Os dados
dos atributos de rota são fornecidos pela IRouteTemplateProvider implementação.
Convenções:

Pode ser gravado para modificar o modelo de aplicativo para personalizar o


comportamento do roteamento.
São lidos na inicialização do aplicativo.

Esta seção mostra um exemplo básico de personalização do roteamento usando o


modelo de aplicativo. O código a seguir faz as rotas se alinharem aproximadamente
com a estrutura de pastas do projeto.

C#

public class NamespaceRoutingConvention : Attribute,


IControllerModelConvention
{
private readonly string _baseNamespace;

public NamespaceRoutingConvention(string baseNamespace)


{
_baseNamespace = baseNamespace;
}

public void Apply(ControllerModel controller)


{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel
!= null);
if (hasRouteAttributes)
{
return;
}

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]/[action]/{id?}");

foreach (var selector in controller.Selectors)


{
selector.AttributeRouteModel = new AttributeRouteModel()
{
Template = template.ToString()
};
}
}
}

O código a seguir impede que a namespace convenção seja aplicada aos controladores
que são roteado por atributo:

C#

public void Apply(ControllerModel controller)


{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel !=
null);
if (hasRouteAttributes)
{
return;
}

Por exemplo, o controlador a seguir não usa NamespaceRoutingConvention :

C#

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
// /managers/index
public IActionResult Index()
{
var template =
ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
return Content($"Index- template:{template}");
}

public IActionResult List(int? id)


{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
O método NamespaceRoutingConvention.Apply :

Não fará nada se o controlador for roteado por atributo.


Define o modelo de controladores com base no namespace . com a base namespace
removida.

O NamespaceRoutingConvention valor pode ser aplicado em Program.cs :

C#

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(
new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});

var app = builder.Build();

Por exemplo, considere o seguinte controlador:

C#

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
public class UsersController : Controller
{
// GET /admin/controllers/users/index
public IActionResult Index()
{
var fullname = typeof(UsersController).FullName;
var template =

ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var path = Request.Path.Value;

return Content($"Path: {path} fullname: {fullname} template:


{template}");
}

public IActionResult List(int? id)


{
var path = Request.Path.Value;
return Content($"Path: {path} ID:{id}");
}
}
}

No código anterior:

A base namespace é My.Application .


O nome completo do controlador anterior é
My.Application.Admin.Controllers.UsersController .
Define NamespaceRoutingConvention o modelo de controladores como
Admin/Controllers/Users/[action]/{id? .

Também NamespaceRoutingConvention pode ser aplicado como um atributo em um


controlador:

C#

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
// /admin/controllers/test/index
public IActionResult Index()
{
var template =
ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var actionname = ControllerContext.ActionDescriptor.ActionName;
return Content($"Action- {actionname} template:{template}");
}

public IActionResult List(int? id)


{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}

Roteamento misto: roteamento de atributo


versus roteamento convencional
ASP.NET Core aplicativos podem misturar o uso de roteamento convencional e
roteamento de atributo. É comum usar rotas convencionais para controladores que
servem páginas HTML para navegadores e roteamento de atributo para controladores
que atendem REST APIs.

As ações são roteadas convencionalmente ou segundo os atributos. Colocar uma rota


no controlador ou na ação faz com que ela seja roteada segundo o atributo. Ações que
definem rotas de atributo não podem ser acessadas por meio das rotas convencionais e
vice-versa. Qualquer atributo de rota no controlador torna todas as ações no atributo
do controlador roteada.

O roteamento de atributo e o roteamento convencional usam o mesmo mecanismo de


roteamento.

Geração de URL e valores de ambiente


Os aplicativos podem usar recursos de geração de URL de roteamento para gerar links
de URL para ações. Gerar URLs elimina URLs de codificação rígida , tornando o código
mais robusto e mantenedível. Esta seção se concentra nos recursos de geração de URL
fornecidos pelo MVC e abrange apenas os conceitos básicos de como a geração de URL
funciona. Consulte Roteamento para obter uma descrição detalhada da geração de URL.

A IUrlHelper interface é o elemento subjacente da infraestrutura entre o MVC e o


roteamento para a geração de URL. Uma instância está IUrlHelper disponível por meio
da Url propriedade em controladores, exibições e componentes de exibição.

No exemplo a seguir, a IUrlHelper interface é usada por meio da Controller.Url


propriedade para gerar uma URL para outra ação.

C#

public class UrlGenerationController : Controller


{
public IActionResult Source()
{
// Generates /UrlGeneration/Destination
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}

public IActionResult Destination()


{
return ControllerContext.MyDisplayRouteInfo();
}
}

Se o aplicativo estiver usando a rota convencional padrão, o valor da variável será a


cadeia /UrlGeneration/Destination de url caracteres de caminho de URL. Esse caminho
de URL é criado pelo roteamento combinando:

Os valores de rota da solicitação atual, que são chamados de valores de ambiente.


Os valores passados Url.Action e substituindo esses valores no modelo de rota:
text

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:

Use um valor padrão se ele tiver um.


Seja ignorado se for opcional. Por exemplo, o id modelo de rota
{controller}/{action}/{id?} .

A geração de URL falhará se qualquer parâmetro de rota necessá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 anterior de Url.Action pressupõe o roteamento convencional. A geração de


URL funciona da mesma forma com o roteamento de atributos, embora os conceitos
sejam diferentes. Com o roteamento convencional:

Os valores de rota são usados para expandir um modelo.


Os valores de rota para controller e action geralmente aparecem nesse modelo.
Isso funciona porque as URLs correspondem ao roteamento aderir a uma
convenção.

O exemplo a seguir usa o roteamento de atributo:

C#

public class UrlGenerationAttrController : Controller


{
[HttpGet("custom")]
public IActionResult Source()
{
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}

[HttpGet("custom/url/to/destination")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

A Source ação no código anterior gera custom/url/to/destination .

LinkGeneratorfoi adicionado em ASP.NET Core 3.0 como uma alternativa a IUrlHelper .


LinkGenerator oferece funcionalidades semelhantes, mas mais flexíveis. Cada método

também IUrlHelper tem uma família correspondente de métodos LinkGenerator .

Gerando URLs pelo nome da ação


Url.Action, LinkGenerator.GetPathByAction e todas as sobrecargas relacionadas foram
projetadas para gerar o ponto de extremidade de destino especificando um nome de
controlador e um nome de ação.

Ao usar Url.Action , os valores de rota atuais são controller action fornecidos pelo
runtime:

O valor de controller e action fazem parte de valores e valores de ambiente . O


método Url.Action sempre usa os valores atuais e action controller gera um
caminho de URL que roteia para a ação atual.

O roteamento tenta usar os valores em valores ambientes para preencher informações


que não foram fornecidas ao gerar uma URL. Considere uma rota como
{a}/{b}/{c}/{d} com valores { a = Alice, b = Bob, c = Carol, d = David } de

ambiente:

O roteamento tem informações suficientes para gerar uma URL sem valores
adicionais.
O roteamento tem informações suficientes porque todos os parâmetros de rota
têm um valor.

Se o valor { d = Donovan } for adicionado:

O valor { d = David } é ignorado.


O caminho de URL gerado é Alice/Bob/Carol/Donovan .

Aviso: os caminhos de URL são hierárquicos. No exemplo anterior, se o valor { c =


Cheryl } for adicionado:

Ambos os valores são ignorados { c = Carol, d = David } .


Não há mais um valor para d e a geração de URL falha.
Os valores desejados e c d devem ser especificados para gerar uma URL.

Talvez você espere atingir esse problema com a rota


{controller}/{action}/{id?} padrão. Esse problema é raro na prática porque

Url.Action sempre especifica explicitamente um e action um controller valor.

Várias sobrecargas de Url.Action tomam um objeto de valores de rota para fornecer


valores para parâmetros de rota diferentes controller e action . O objeto de valores de
rota é usado com frequência com id . Por exemplo, Url.Action("Buy", "Products", new
{ id = 17 }) . O objeto de valores de rota:

Por convenção geralmente é um objeto de tipo anônimo.


Pode ser um IDictionary<> ou um POCO ).

Qualquer valor de rota adicional que não corresponder aos parâmetros de rota será
colocado na cadeia de caracteres de consulta.

C#

public IActionResult Index()


{
var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
return Content(url!);
}

O código anterior gera /Products/Buy/17?color=red .

O código a seguir gera uma URL absoluta:

C#

public IActionResult Index2()


{
var url = Url.Action("Buy", "Products", new { id = 17 }, protocol:
Request.Scheme);
// Returns https://localhost:5001/Products/Buy/17
return Content(url!);
}

Para criar uma URL absoluta, use uma das seguintes opções:

Uma sobrecarga que aceita um protocol . Por exemplo, o código anterior.


LinkGenerator.GetUriByAction, que gera URIs absolutas por padrão.

Gerar URLs por rota


O código anterior demonstrou a geração de uma URL passando o controlador e o nome
da ação. IUrlHelper também fornece a família de métodos Url.RouteUrl . Esses métodos
são semelhantes a Url.Action, mas não copiam os valores atuais de e controller para os
valores de action rota. O uso mais comum de Url.RouteUrl :

Especifica um nome de rota para gerar a URL.


Geralmente não especifica um controlador ou nome de ação.

C#

public class UrlGeneration2Controller : Controller


{
[HttpGet("")]
public IActionResult Source()
{
var url = Url.RouteUrl("Destination_Route");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}

[HttpGet("custom/url/to/destination2", Name = "Destination_Route")]


public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}

O arquivo a seguir Razor gera um link HTML para o Destination_Route :

CSHTML

<h1>Test Links</h1>

<ul>
<li><a href="@Url.RouteUrl("Destination_Route")">Test
Destination_Route</a></li>
</ul>

Gerar URLs em HTML e Razor


IHtmlHelper fornece os HtmlHelper métodos Html.BeginForm e Html.ActionLink para
gerar <form> e <a> elementos, respectivamente. Esses métodos usam o método
Url.Action para gerar uma URL e aceitam argumentos semelhantes. O complementos
Url.RouteUrl para HtmlHelper são Html.BeginRouteForm e Html.RouteLink , que têm
uma funcionalidade semelhante.

TagHelpers geram URLs por meio do TagHelper form e do TagHelper <a> . Ambos usam
IUrlHelper para sua implementação. Consulte auxiliares de marca nos 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.

Geração de URL nos Resultados da Ação


Os exemplos anteriores mostraram o uso IUrlHelper em um controlador. O uso mais
comum em um controlador é gerar uma URL como parte de um resultado de 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 é redirecionar
depois de aceitar a entrada do usuário:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
if (ModelState.IsValid)
{
// Update DB with new details.
ViewData["Message"] = $"Successful edit of customer {id}";
return RedirectToAction("Index");
}
return View(customer);
}

A ação resulta em métodos de fábrica, como RedirectToAction e CreatedAtAction


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
uma rota convencional dedicada. No exemplo a seguir, a rota nomeada blog é uma rota
convencional dedicada:

C#

app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Usando as definições de rota anteriores, Url.Action("Index", "Home") gera o caminho
/ da URL usando a default rota, 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 .

As rotas convencionais dedicadas dependem de um comportamento especial de valores


padrão que não têm um parâmetro de rota correspondente que impede que a rota seja
muito gananciosa com a geração de URL. 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
falha porque os valores { controller = Home, action = Index } não correspondem {
controller = Blog, action = Article } . O roteamento, então, faz o fallback para tentar
default , que é bem-sucedido.

Áreas
As áreas são um recurso MVC usado para organizar a funcionalidade relacionada em um
grupo como um grupo separado:

Namespace de roteamento para ações do controlador.


Estrutura de pastas para exibições.

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 discute como o roteamento interage com á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 area
rota para um area nome: Blog

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

app.Run();

No código anterior, MapAreaControllerRoute é chamado para criar o "blog_route" . O


segundo parâmetro, "Blog" é o nome da área.

Ao corresponder a um caminho de URL como /Manage/Users/AddUser , a "blog_route"


rota gera os valores { area = Blog, controller = Users, action = AddUser } de rota. O
area valor da rota é produzido por um valor padrão para area . A rota criada por

MapAreaControllerRoute ela é equivalente ao seguinte:

C#

app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog"
});
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

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

O roteamento convencional é dependente da ordem. Em geral, as rotas com áreas


devem ser colocadas anteriormente, pois são mais específicas do que as rotas sem uma
área.

Usando o exemplo anterior, os valores de rota correspondem { area = Blog,


controller = Users, action = AddUser } à seguinte ação:

C#

using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area =
ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName =
ControllerContext.ActionDescriptor.ControllerName;

return Content($"area name:{area}" +


$" controller:{controllerName} action name: {actionName}");
}
}
}

O atributo [Área] é o que indica um controlador como parte de uma área. Esse
controlador está na Blog área. Os controladores sem um [Area] atributo não são
membros de nenhuma área e não correspondem quando o valor da area rota é
fornecido pelo roteamento. No exemplo a seguir, somente o primeiro controlador
listado pode corresponder aos valores de rota { area = Blog, controller = Users,
action = AddUser } .

C#

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area =
ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName =
ControllerContext.ActionDescriptor.ControllerName;

return Content($"area name:{area}" +


$" controller:{controllerName} action name: {actionName}");
}
}
}
C#

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
// Matches { area = Zebra, controller = Users, action = AddUser }
[Area("Zebra")]
public class UsersController : Controller
{
// GET /zebra/users/adduser
public IActionResult AddUser()
{
var area =
ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName =
ControllerContext.ActionDescriptor.ControllerName;

return Content($"area name:{area}" +


$" controller:{controllerName} action name: {actionName}");
}
}
}

C#

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
{
// GET /users/adduser
public IActionResult AddUser()
{
var area =
ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName =
ControllerContext.ActionDescriptor.ControllerName;

return Content($"area name:{area}" +


$" controller:{controllerName} action name: {actionName}");
}
}
}
O namespace de cada controlador é mostrado aqui para integridade. Se os
controladores anteriores usassem o mesmo namespace, um erro do compilador seria
gerado. 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.

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 area da rota está disponível como
um valor ambiente para roteamento a ser usado 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.

C#

app.MapAreaControllerRoute(name: "duck_route",
areaName: "Duck",
pattern:
"Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
pattern:
"Manage/{controller=Home}/{action=Index}/{id?}");

C#

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
[Area("Duck")]
public class UsersController : Controller
{
// GET /Manage/users/GenerateURLInArea
public IActionResult GenerateURLInArea()
{
// Uses the 'ambient' value of area.
var url = Url.Action("Index", "Home");
// Returns /Manage/Home/Index
return Content(url);
}

// GET /Manage/users/GenerateURLOutsideOfArea
public IActionResult GenerateURLOutsideOfArea()
{
// Uses the empty value for area.
var url = Url.Action("Index", "Home", new { area = "" });
// Returns /Manage
return Content(url);
}
}
}

O código a seguir gera uma URL para /Zebra/Users/AddUser :

C#

public class HomeController : Controller


{
public IActionResult About()
{
var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
return Content($"URL: {url}");
}

Definição de ação
Métodos públicos em um controlador, exceto aqueles com o atributo NonAction , são
ações.

Código de exemplo
MyDisplayRouteInfo é fornecido pelo pacote NuGet
Rick.Docs.Samples.RouteInfo e exibe informações de rota.
Exibir ou baixar código de exemplo (como baixar)

Diagnóstico de depuração
Para obter uma saída de diagnóstico de roteamento detalhada, defina
Logging:LogLevel:Microsoft como Debug . No ambiente de desenvolvimento, defina o

nível de log em appsettings.Development.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Injeção de dependência em
controladores no ASP.NET Core
Artigo • 04/01/2023 • 5 minutos para o fim da leitura

Por Shadi Namrouti , Rick Anderson e Steve Smith

Controladores do ASP.NET Core MVC solicitam dependências explicitamente por meio


de construtores. O ASP.NET Core tem suporte interno para DI (injeção de dependência).
A injeção de dependência torna mais fácil testar e manter aplicativos.

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

Injeção de construtor
Os serviços são adicionados como um parâmetro de construtor e o runtime resolve o
serviço de contêiner de serviço. Os serviços são normalmente definidos usando
interfaces. Por exemplo, considere um aplicativo que exige a hora atual. A interface a
seguir expõe o serviço IDateTime :

C#

public interface IDateTime


{
DateTime Now { get; }
}

O código a seguir implementa a interface IDateTime :

C#

public class SystemDateTime : IDateTime


{
public DateTime Now
{
get { return DateTime.Now; }
}
}

Adicione o serviço ao contêiner de serviço:

C#
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDateTime, SystemDateTime>();

services.AddControllersWithViews();
}

Para obter mais informações sobre AddSingleton, veja tempos de vida do serviço DI.

O código a seguir exibe uma saudação ao usuário com base na hora do dia:

C#

public class HomeController : Controller


{
private readonly IDateTime _dateTime;

public HomeController(IDateTime dateTime)


{
_dateTime = dateTime;
}

public IActionResult Index()


{
var serverTime = _dateTime.Now;
if (serverTime.Hour < 12)
{
ViewData["Message"] = "It's morning here - Good Morning!";
}
else if (serverTime.Hour < 17)
{
ViewData["Message"] = "It's afternoon here - Good Afternoon!";
}
else
{
ViewData["Message"] = "It's evening here - Good Evening!";
}
return View();
}

Execute o aplicativo e será exibida uma mensagem com base no horário.

Injeção de ação com FromServices


O FromServicesAttribute habilita a injeção de um serviço diretamente em um método
de ação sem usar a injeção de construtor:

C#
public IActionResult About([FromServices] IDateTime dateTime)
{
return Content( $"Current server time: {dateTime.Now}");
}

Acessar configurações de um controlador


Acessar definições de configuração ou do aplicativo de dentro de um controlador é um
padrão comum. O padrão de opções descrito no padrão Opções em ASP.NET Core é a
abordagem preferencial para gerenciar configurações. Em geral, não convém injetar
IConfiguration diretamente em um controlador.

Crie uma classe que representa as opções. Por exemplo:

C#

public class SampleWebSettings


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

Adicione a classe de configuração à coleção de serviços:

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddSingleton<IDateTime, SystemDateTime>();
services.Configure<SampleWebSettings>(Configuration);

services.AddControllersWithViews();
}

Configure o aplicativo para ler as configurações de um JSarquivo formatado em ON:

C#

public class Program


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

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("samplewebsettings.json",
optional: false,
reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}

O código a seguir solicita as configurações IOptions<SampleWebSettings> do contêiner


de serviço e usa-as no método Index :

C#

public class SettingsController : Controller


{
private readonly SampleWebSettings _settings;

public SettingsController(IOptions<SampleWebSettings> settingsOptions)


{
_settings = settingsOptions.Value;
}

public IActionResult Index()


{
ViewData["Title"] = _settings.Title;
ViewData["Updates"] = _settings.Updates;
return View();
}
}

Recursos adicionais
Consulte a lógica do controlador de teste em ASP.NET Core para saber como
tornar o código mais fácil de testar solicitando explicitamente dependências em
controladores.

Substitui o contêiner de injeção de dependência padrão por uma implementação


de terceiros.
Injeção de dependência em exibições no
ASP.NET Core
Artigo • 04/01/2023 • 9 minutos para o fim da leitura

O ASP.NET Core dá suporte à injeção de dependência em exibições. Isso pode ser útil
para serviços específicos a uma exibição, como localização ou dados necessários apenas
para o preenchimento de elementos de exibição. A maioria das exibições de dados deve
ser passada do controlador.

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

Injeção de configuração
Os valores em arquivos de configurações, como appsettings.json e
appsettings.Development.json , podem ser injetados em um modo de exibição.
Considere o appsettings.Development.json código de exemplo :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"MyRoot": {
"MyParent": {
"MyChildName": "Joe"
}
}
}

A marcação a seguir exibe o valor de configuração em um Razor modo de exibição


Páginas:

CSHTML

@page
@model PrivacyModel
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
ViewData["Title"] = "Privacy RP";
}
<h1>@ViewData["Title"]</h1>

<p>PR Privacy</p>

<h2>
MyRoot:MyParent:MyChildName:
@Configuration["MyRoot:MyParent:MyChildName"]
</h2>

A marcação a seguir exibe o valor de configuração em um modo de exibição MVC:

CSHTML

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
ViewData["Title"] = "Privacy MVC";
}
<h1>@ViewData["Title"]</h1>

<p>MVC Use this page to detail your site's privacy policy.</p>

<h2>
MyRoot:MyParent:MyChildName:
@Configuration["MyRoot:MyParent:MyChildName"]
</h2>

Para obter mais informações, consulte Configuração no ASP.NET Core

Injeção de serviço
Um serviço pode ser injetado em uma exibição usando a diretiva @inject .

CSHTML

@using System.Threading.Tasks
@using ViewInjectSample.Model
@using ViewInjectSample.Model.Services
@model IEnumerable<ToDoItem>
@inject StatisticsService StatsService
<!DOCTYPE html>
<html>
<head>
<title>To Do Items</title>
</head>
<body>
<div>
<h1>To Do Items</h1>
<ul>
<li>Total Items: @StatsService.GetCount()</li>
<li>Completed: @StatsService.GetCompletedCount()</li>
<li>Avg. Priority: @StatsService.GetAveragePriority()</li>
</ul>
<table>
<tr>
<th>Name</th>
<th>Priority</th>
<th>Is Done?</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>@item.Name</td>
<td>@item.Priority</td>
<td>@item.IsDone</td>
</tr>
}
</table>
</div>
</body>
</html>

Essa exibição exibe uma lista de instâncias ToDoItem , junto com um resumo mostrando
estatísticas gerais. O resumo é populado com base no StatisticsService injetado. Esse
serviço é registrado para injeção de dependência em ConfigureServices Program.cs :

C#

using ViewInjectSample.Helpers;
using ViewInjectSample.Infrastructure;
using ViewInjectSample.Interfaces;
using ViewInjectSample.Model.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
builder.Services.AddTransient<StatisticsService>();
builder.Services.AddTransient<ProfileOptionsService>();
builder.Services.AddTransient<MyHtmlHelper>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.MapRazorPages();

app.MapDefaultControllerRoute();

app.Run();

O StatisticsService faz alguns cálculos no conjunto de instâncias ToDoItem , que ele


acessa por meio de um repositório:

C#

using System.Linq;
using ViewInjectSample.Interfaces;

namespace ViewInjectSample.Model.Services
{
public class StatisticsService
{
private readonly IToDoItemRepository _toDoItemRepository;

public StatisticsService(IToDoItemRepository toDoItemRepository)


{
_toDoItemRepository = toDoItemRepository;
}

public int GetCount()


{
return _toDoItemRepository.List().Count();
}

public int GetCompletedCount()


{
return _toDoItemRepository.List().Count(x => x.IsDone);
}

public double GetAveragePriority()


{
if (_toDoItemRepository.List().Count() == 0)
{
return 0.0;
}

return _toDoItemRepository.List().Average(x => x.Priority);


}
}
}
O repositório de exemplo usa uma coleção em memória. Uma implementação na
memória não deve ser usada para conjuntos de dados grandes e acessados
remotamente.

A amostra exibe dados do modelo associado à exibição e o serviço injetado na exibição:

Populando os dados de pesquisa


A injeção de exibição pode ser útil para popular opções em elementos de interface do
usuário, como listas suspensas. Considere um formulário de perfil do usuário que inclui
opções para especificar o gênero, estado e outras preferências. Renderizar esse
formulário usando uma abordagem padrão pode exigir o controlador ou Razor a página
para:

Solicite serviços de acesso a dados para cada um dos conjuntos de opções.


Preencha um modelo ou ViewBag com cada conjunto de opções a serem
associadas.

Uma abordagem alternativa injeta os serviços diretamente na exibição para obter as


opções. Isso minimiza a quantidade de código exigida pelo controlador ou pela página
razor, movendo essa lógica de construção de elemento de exibição para a própria
exibição. A ação do controlador ou Razor a página para exibir um formulário de edição
de perfil só precisa passar o formulário da instância de perfil:

C#

using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;
namespace ViewInjectSample.Controllers;

public class ProfileController : Controller


{
public IActionResult Index()
{
// A real app would up profile based on the user.
var profile = new Profile()
{
Name = "Rick",
FavColor = "Blue",
Gender = "Male",
State = new State("Ohio","OH")
};
return View(profile);
}
}

O formulário HTML usado para atualizar as 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:

CSHTML

@using System.Threading.Tasks
@using ViewInjectSample.Model.Services
@model ViewInjectSample.Model.Profile
@inject ProfileOptionsService Options
<!DOCTYPE html>
<html>
<head>
<title>Update Profile</title>
</head>
<body>
<div>
<h1>Update Profile</h1>
Name: @Html.TextBoxFor(m => m.Name)
<br/>
Gender: @Html.DropDownList("Gender",
Options.ListGenders().Select(g =>
new SelectListItem() { Text = g, Value = g }))
<br/>

State: @Html.DropDownListFor(m => m.State!.Code,


Options.ListStates().Select(s =>
new SelectListItem() { Text = s.Name, Value = s.Code}))
<br />

Fav. Color: @Html.DropDownList("FavColor",


Options.ListColors().Select(c =>
new SelectListItem() { Text = c, Value = c }))
</div>
</body>
</html>

O ProfileOptionsService é um serviço no nível da interface do usuário criado para


fornecer apenas os dados necessários para esse formulário:

C#

namespace ViewInjectSample.Model.Services;

public class ProfileOptionsService


{
public List<string> ListGenders()
{
// Basic sample
return new List<string>() {"Female", "Male"};
}

public List<State> ListStates()


{
// Add a few states
return new List<State>()
{
new State("Alabama", "AL"),
new State("Alaska", "AK"),
new State("Ohio", "OH")
};
}

public List<string> ListColors()


{
return new List<string>() { "Blue","Green","Red","Yellow" };
}
}

Observe que um tipo não registrado gera uma exceção no runtime porque o provedor
de serviços é consultado internamente por meio GetRequiredServicede .
Substituindo serviços
Além de injetar novos serviços, essa técnica 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:

Os campos padrão incluem Html , Component e Url . Para substituir os Auxiliares HTML
padrão por uma versão personalizada, use @inject :

CSHTML

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

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 de
unidade em ASP.NET Core
Artigo • 26/09/2022 • 26 minutos para o fim da leitura

Por Steve Smith

Os testes de unidade envolvem o teste de uma parte de um aplicativo isoladamente em


relação à sua infraestrutura e às suas dependências. Quando a unidade está testando a
lógica do controlador, somente o conteúdo de uma única ação é testada, não o
comportamento de suas dependências ou da estrutura em si.

Controladores de teste de unidade


Configure testes de unidade de ações do controlador para se concentrarem no
comportamento do controlador. Um teste de unidade do controlador evita cenários
como filtros, roteamento ou model binding. Os testes que abrangem as interações entre
os componentes que respondem coletivamente a uma solicitação são manipulados
pelos testes de integração. Para obter mais informações sobre testes de integração,
consulte testes de integração em ASP.NET Core.

Se você estiver escrevendo filtros e rotas personalizados, realize testes de unidade neles
de forma isolada, não como parte dos testes em uma ação do controlador específica.

Para demonstrar testes de unidade do controlador, examine o controlador a seguir no


aplicativo de exemplo.

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

O Home controlador exibe uma lista de sessões de brainstorming e permite a criação de


novas sessões de brainstorming com uma solicitação POST:

C#

public class HomeController : Controller


{
private readonly IBrainstormSessionRepository _sessionRepository;

public HomeController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

public async Task<IActionResult> Index()


{
var sessionList = await _sessionRepository.ListAsync();

var model = sessionList.Select(session => new


StormSessionViewModel()
{
Id = session.Id,
DateCreated = session.DateCreated,
Name = session.Name,
IdeaCount = session.Ideas.Count
});

return View(model);
}

public class NewSessionModel


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

[HttpPost]
public async Task<IActionResult> Index(NewSessionModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
await _sessionRepository.AddAsync(new BrainstormSession()
{
DateCreated = DateTimeOffset.Now,
Name = model.SessionName
});
}

return RedirectToAction(actionName: nameof(Index));


}
}

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

C#

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

C#

private List<BrainstormSession> GetTestSessions()


{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
});
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
});
return sessions;
}

Os Home testes de método do HTTP POST Index controlador verificam que:

Quando ModelState.IsValid é false , o método de ação retorna uma 400


Solicitação inválidaViewResult com os dados apropriados.
Quando ModelState.IsValid é true :
O método Add no repositório é chamado.
Um RedirectToActionResult é retornado com os argumentos corretos.

O estado de modelo inválido é testado por meio da adição de erros usando


AddModelError, conforme mostrado no primeiro teste abaixo:

C#

[Fact]
public async Task
IndexPost_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions());
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();

// Act
var result = await controller.Index(newSession);

// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}

[Fact]
public async Task
IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
};
// Act
var result = await controller.Index(newSession);

// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>
(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}

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.

7 Observação

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
instrução final return retorna uma nova StormSessionViewModel exibição
( Controllers/SessionController.cs ):

C#
public class SessionController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;

public SessionController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

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


{
if (!id.HasValue)
{
return RedirectToAction(actionName: nameof(Index),
controllerName: "Home");
}

var session = await _sessionRepository.GetByIdAsync(id.Value);


if (session == null)
{
return Content("Session not found.");
}

var viewModel = new StormSessionViewModel()


{
DateCreated = session.DateCreated,
Name = session.Name,
Id = session.Id
};

return View(viewModel);
}
}

Os testes de unidade incluem um teste para cada cenário return na ação Index do
controlador Session:

C#

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

C#

[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}

var result = session.Ideas.Select(idea => new IdeaDTO()


{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();

return Ok(result);
}

[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var session = await _sessionRepository.GetByIdAsync(model.SessionId);


if (session == null)
{
return NotFound(model.SessionId);
}

var idea = new Idea()


{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);

await _sessionRepository.UpdateAsync(session);

return Ok(session);
}
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:

C#

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

C#
[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 do modelo ou a associação de modelo em testes de
unidade , basta testar o comportamento do método de ação quando confrontado com
um inválido ModelState :

C#

[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:
C#

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

O terceiro teste Create , Create_ReturnsNewlyCreatedIdeaForSession , verifica se o


método UpdateAsync do repositório é chamado. A simulação é chamada com
Verifiable e o método Verify do repositório fictício é chamado para confirmar se o

método verificável é executado. Não é responsabilidade do teste de unidade garantir


que o UpdateAsync método salvou os dados, que podem ser executados com um teste
de integração.

C#

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

var newIdea = new NewIdeaModel()


{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();
// Act
var result = await controller.Create(newIdea);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription,
returnSession.Ideas.LastOrDefault().Description);
}

Testar ActionResult<T>
No ASP.NET Core 2.1 ou posterior, o ActionResult<T> (ActionResult<TValue>) permite
que você retorne um tipo derivado 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:

C#

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

var result = session.Ideas.Select(idea => new IdeaDTO()


{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();

return result;
}

Dois testes do controlador ForSessionActionResult estão incluídos no


ApiIdeasControllerTests .
O primeiro teste confirma se o controlador retorna um ActionResult , mas não uma lista
de ideias inexistente para uma sessão id inexistente:

O tipo ActionResult<List<IdeaDTO>> é ActionResult .


O Result é um NotFoundObjectResult.

C#

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

Para uma sessão id válida, o segundo teste confirma se o método retorna:

Um ActionResult com um tipo List<IdeaDTO> .


O ActionResult<T>.Value é um tipo List<IdeaDTO> .
O primeiro item na lista é uma ideia válida que corresponde à ideia armazenada na
sessão fictícia (obtida chamando GetTestSession ).

C#

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

C#

[HttpPost("createactionresult")]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<ActionResult<BrainstormSession>>
CreateActionResult([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var session = await _sessionRepository.GetByIdAsync(model.SessionId);

if (session == null)
{
return NotFound(model.SessionId);
}

var idea = new Idea()


{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);

await _sessionRepository.UpdateAsync(session);

return CreatedAtAction(nameof(CreateActionResult), new { id = session.Id


}, session);
}

Três testes CreateActionResult estão incluídos no ApiIdeasControllerTests .

O primeiro texto confirma que um BadRequest é retornado para um modelo inválido.


C#

[Fact]
public async Task CreateActionResult_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.CreateActionResult(model: null);

// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>
(result);
Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}

O segundo teste verifica se um NotFound será retornado se a sessão não existir.

C#

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

var newIdea = new NewIdeaModel()


{
Description = testDescription,
Name = testName,
SessionId = nonExistentSessionId
};

// Act
var result = await controller.CreateActionResult(newIdea);

// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>
(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}

Para uma sessão id válida, o teste final confirmará se:


O método retorna um ActionResult com um tipo BrainstormSession .
O ActionResult<T>.Result é um CreatedAtActionResult. CreatedAtActionResult é
semelhante à resposta 201 Criado com um cabeçalho Location .
O ActionResult<T>.Value é um tipo BrainstormSession .
A chamada fictícia para atualizar a sessão, UpdateAsync(testSession) , foi invocada.
A chamada de método Verifiable é verificada por meio da execução de
mockRepo.Verify() nas declarações.
Dois objetos Idea são retornados para a sessão.
O último item (o Idea adicionado pela chamada fictícia a UpdateAsync )
corresponde ao newIdea adicionado à sessão no teste.

C#

[Fact]
public async Task CreateActionResult_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);

var newIdea = new NewIdeaModel()


{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();

// Act
var result = await controller.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
Criar e executar testes de unidade com o Visual Studio
MyTested.AspNetCore.Mvc – Biblioteca de Testes Fluentes para ASP.NET Core
MVC : biblioteca de testes de unidade fortemente tipada, fornecendo uma
interface fluente para testar aplicativos MVC e API Web. (Não mantido ou com
suporte da Microsoft.)
JustMockLite : uma estrutura de zombaria para desenvolvedores do .NET. (Não
mantido ou com suporte da Microsoft.)
ASP.NET Core Blazor
Artigo • 26/09/2022 • 9 minutos para o fim da leitura

Boas vindas ao Blazor!

O Blazor é uma estrutura para criar uma interface do usuário web interativa do lado do
cliente com o .NET:

Crie interfaces do usuário interativas avançadas usando C# em vez de JavaScript .


Compartilhe a lógica de aplicativo do lado do cliente e do servidor gravada no
.NET.
Renderize a interface do usuário, como HTML e CSS para suporte amplo de
navegadores, incluindo navegadores móveis.
Integre-se a plataformas de hospedagem modernas, como o Docker.
Crie aplicativos móveis e de área de trabalho híbrida com .NET e Blazor.

Usar o .NET para desenvolvimento web do lado do cliente oferece as seguintes


vantagens:

escreva o código em C# em vez de JavaScript.


Aproveite o ecossistema .NET existente das bibliotecas .NET.
Compartilhe a lógica de aplicativo entre o servidor e o cliente.
Beneficie-se com o desempenho, confiabilidade e segurança do .NET.
Mantenha-se produtivo no Windows, Linux ou macOS com um ambiente de
desenvolvimento, como o Visual Studio ou o Visual Studio Code .
Crie um conjunto comum de linguagens, estruturas e ferramentas que são estáveis,
com recursos avançados e fáceis de usar.

7 Observação

Para obter um tutorial de início rápido Blazor, consulte Criar seu primeiro
aplicativo Blazor .

Componentes
Os aplicativos Blazor se baseiam em componentes. Um componente no Blazor é um
elemento da interface do usuário, como uma página, uma caixa de diálogo ou um
formulário de entrada de dados.

Os componentes são classes C# do .NET incorporadas a assemblies .NET que:


Definem a lógica de renderização da interface de usuário flexível.
Tratam eventos do usuário.
Podem ser aninhados e reutilizados.
Podem ser compartilhados e distribuído como bibliotecas de classes Razor ou
pacotes do NuGet.

A classe do componente geralmente é gravada na forma de uma página de marcação


do Razor com uma extensão de arquivo .razor . Componentes em Blazor são chamados
formalmente de componentes Razor, e informalmente como componentes Blazor. O
Razor é uma sintaxe para a combinação de marcação HTML com o código C# projetada
para melhorar a produtividade do desenvolvedor. O Razor permite que você alterne
entre a marcação HTML e C# no mesmo arquivo com suporte para programação com
IntelliSense no Visual Studio. Razor Pages e MVC também usam Razor. Ao contrário de
Razor Pages e MVC, que são criadas ao redor de um modelo de solicitação/resposta, os
componentes são usados especificamente para composição e lógica da interface do
usuário do lado do cliente.

O Blazor usa marcações HTML naturais para composição de interface do usuário. A


seguinte marcação Razor demonstra um componente ( Dialog.razor ) que exibe uma
caixa de diálogo e processa um evento quando o usuário seleciona um botão:

razor

<div class="card" style="width:22rem">


<div class="card-body">
<h3 class="card-title">@Title</h3>
<p class="card-text">@ChildContent</p>
<button @onclick="OnYes">Yes!</button>
</div>
</div>

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }

[Parameter]
public string? Title { get; set; }

private void OnYes()


{
Console.WriteLine("Write to the console in C#! 'Yes' button
selected.");
}
}

No exemplo anterior, OnYes é um método C# disparado pelo evento onclick do botão.


O texto da caixa de diálogo ( ChildContent ) e o título ( Title ) são fornecidos pelo
componente que usa esse componente em sua interface do usuário.

O componente Dialog está aninhado em outro componente usando uma marca HTML.
No exemplo a seguir, o componente Index ( Pages/Index.razor ) usa o componente
Dialog anterior. O atributo Title da marca passa um valor para o título para a
propriedade Title do componente Dialog . O texto do componente Dialog
( ChildContent ) é definido pelo conteúdo do elemento <Dialog> . Quando o
componente Dialog é adicionado ao componente Index , o IntelliSense no Visual Studio
acelera o desenvolvimento com o preenchimento de sintaxe e de parâmetro.

razor

@page "/"

<h1>Hello, world!</h1>

<p>
Welcome to your new app.
</p>

<Dialog Title="Learn More">


Do you want to <i>learn more</i> about Blazor?
</Dialog>

A caixa de diálogo é renderizada quando o componente Index é acessado em um


navegador. Quando o botão é selecionado pelo usuário, o console de ferramentas para
desenvolvedores do navegador mostra a mensagem gravada pelo método OnYes :
Os componentes são renderizados em uma representação na memória do Modelo de
Objeto do Documento (DOM) do navegador chamada árvore de renderização, que é
usada para atualizar a interface do usuário de maneira flexível e eficiente.

Blazor Server
O Blazor Server dá suporte para hospedar os componentes do Razor no servidor em um
aplicativo ASP.NET Core. As atualizações da interface do usuário são tratadas por uma
conexão SignalR.

O runtime permanece no servidor e processa:

A execução do código C# do aplicativo.


O envio de eventos de interface do usuário do navegador para o servidor.
A aplicação das atualizações de interface do usuário a um componente
renderizado que são enviadas novamente pelo servidor.

A conexão usada pelo Blazor Server para se comunicar com o navegador também é
usada para processar as chamadas de interoperabilidade do JavaScript.
As aplicativos Blazor Server renderizam conteúdo de maneira diferente dos modelos
tradicionais para renderizar a interface do usuário em aplicativos ASP.NET Core usando
exibições Razor ou páginas Razor. Ambos os modelos usam a linguagem Razor para
descrever conteúdo HTML para renderização, mas eles diferem significativamente na
forma como a marcação é renderizada.

Quando uma Razor Page ou exibição é renderizada, cada linha de código Razor emite
HTML no formulário de texto. Após a renderização, o servidor descarta a página ou a
instância de exibição, incluindo qualquer estado que foi produzido. Quando outra
solicitação para a página ocorre, toda a página é renderizada novamente para a HTML e
enviada ao cliente.

Blazor Server produz um grafo de componentes para exibição semelhante a um HTML


ou DOM (Modelo de Objeto do Documento) do XML. O grafo de componente inclui o
estado mantido em propriedades e campos. Blazor avalia o grafo de componente para
produzir uma representação binária da marcação, que é enviada ao cliente para
renderização. Após a conexão ser feita entre o cliente e o servidor, os elementos pré-
renderizados estáticos do componente são substituídos por elementos interativos. A
pré-renderização do conteúdo no servidor faz com que o aplicativo pareça mais
responsivo no cliente.

Depois que os componentes são interativos no cliente, as atualizações da interface do


usuário são disparadas pela interação do usuário e por eventos de aplicativo. Quando
ocorre uma atualização, o grafo do componente é renderizado novamente e uma diff
(diferença) da interface do usuário é calculada. Essa diff é o menor conjunto de edições
do DOM necessárias para atualizar a interface do usuário no cliente. A diff é enviada ao
cliente em um formato binário e aplicada pelo navegador.

Um componente é descartado depois que o usuário navega para longe dele.

Blazor WebAssembly
O Blazor WebAssembly é uma estrutura de SPA (aplicativo de página única) para criar
aplicativos Web do lado do cliente interativos com o .NET. Blazor WebAssembly usa
padrões da Web abertos sem plug-ins ou recompilação de código em outras
linguagens. Blazor WebAssembly funciona em todos os navegadores da Web modernos,
incluindo os navegadores móveis.

A execução do código do .NET em navegadores da Web é possibilitada por


WebAssembly (abreviado como wasm ). O WebAssembly é um formato de código de
bytes compacto, otimizado para download rápido e máxima velocidade de execução. O
WebAssembly é um padrão aberto da Web compatível com navegadores da Web sem
plug-ins.

O código WebAssembly pode acessar a funcionalidade completa do navegador por


meio de JavaScript, chamado de Interoperabilidade do JavaScript, geralmente abreviada
como Interop do JavaScript ou Interop do JS. O código .NET executado por meio da
WebAssembly no navegador é executado na área restrita do JavaScript do navegador
com as proteções que a área restrita oferece contra ações mal intencionadas no
computador cliente.
Quando um aplicativo Blazor WebAssembly é criado e executado em um navegador:

Os arquivos de código C# e do Razor são compilados em assemblies do .NET.


Os assemblies e o runtime do .NET são baixados no navegador.
O Blazor WebAssembly inicializa o runtime do .NET e o configura para carregar os
assemblies no aplicativo. O runtime Blazor WebAssembly usa a interoperabilidade
de JavaScript para manipular as chamadas de API do navegador e as manipulações
DOM.

O tamanho do aplicativo publicado, seu tamanho de payload, é um fator de


desempenho crítico para a utilidade do aplicativo. Um aplicativo grande leva um tempo
relativamente longo para baixar para um navegador, o que afeta a experiência do
usuário. O Blazor WebAssembly otimiza o tamanho do conteúdo para reduzir os tempos
de download:

O código não utilizado é retirado do aplicativo quando publicado pelo Trimmer de


linguagem intermediária (IL).
As respostas HTTP são compactadas.
O runtime do .NET e os assemblies são armazenados em cache no navegador.

Blazor Hybrid
Aplicativos híbridos usam uma combinação de tecnologias nativas e da Web. Um
aplicativo Blazor Hybrid usa Blazor em um aplicativo cliente nativo. Os componentes
Razor são executados nativamente no processo do .NET e renderizam a interface do
usuário da Web em um controle Web View inserido usando um canal de
interoperabilidade local. WebAssembly não é usado em aplicativos híbridos. Os
aplicativos híbridos abrangem as seguintes tecnologias:

.NET Multi-platform App UI (.NET MAUI): uma estrutura multiplataforma para criar
aplicativos móveis e de área de trabalho nativos com C# e XAM.
Windows Presentation Foundation (WPF): uma estrutura de interface do usuário
que é independente de resolução e usa um mecanismo de renderização baseado
em vetor, criado para aproveitar o hardware gráfico moderno.
Windows Forms: uma estrutura de interface do usuário que cria aplicativos cliente
da área de trabalho avançados para Windows. A plataforma de desenvolvimento
Windows Forms dá suporte a uma ampla gama de recursos de desenvolvimento
do aplicativo, incluindo controles, grafos, associação de dados e entrada de
usuário.

Para obter mais informações sobre como criar aplicativos Blazor Hybrid com as
estruturas anteriores, consulte os seguintes artigos:
Modelos de hospedagem do ASP.NET Core Blazor
ASP.NET Core Blazor Hybrid

Interoperabilidade do JavaScript
Para aplicativos que exigem bibliotecas JavaScript e acesso a APIs do navegador de
terceiros, os componentes interoperam com o JavaScript. Os componentes 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#.

Compartilhamento de código e o .NET


Standard
Blazor implementa o .NET Standard, que permite Blazor que os projetos façam
referência a bibliotecas que estejam em conformidade com as especificações do .NET
Standard. O .NET Standard é uma especificação formal das APIs do .NET que são
comuns entre as implementações do .NET. As bibliotecas de classe do .NET Standard
podem ser compartilhadas entre diferentes plataformas do .NET, como Blazor, .NET
Framework, .NET Core, Xamarin, Mono e Unity.

As APIs que não são aplicáveis em um navegador da Web (por exemplo, para acessar o
sistema de arquivos, abrir um soquete e threading) geram a
PlatformNotSupportedException.

Recursos adicionais
WebAssembly
Modelos de hospedagem do ASP.NET Core Blazor
Usar ASP.NET Core SignalR com Blazor
Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor
Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor
Repositório do GitHub mono/mono
Guia do C#
Razor Referência de sintaxe para ASP.NET Core
HTML
Incríveis links da comunidade Blazor
Blazor ASP.NET Core plataformas com
suporte
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Blazor WebAssembly e Blazor Server têm suporte nos navegadores mostrados na tabela
a seguir em plataformas móveis e desktop.

Navegador Versão

Apple Safari Atual†

Google Chrome Atual†

Microsoft Edge Atual†

Mozilla Firefox Atual†

†Current refere-se à versão mais recente do navegador.

Para Blazor Hybrid aplicativos, testamos e damos suporte às versões mais recentes de
controle de plataforma Web View :

Microsoft Edge WebView2 no Windows


Chrome no Android
Safari no iOS e macOS

Recursos adicionais
Modelos de hospedagem do ASP.NET Core Blazor
SignalR ASP.NET Core plataformas com suporte
Ferramentas para ASP.NET Core Blazor
Artigo • 28/11/2022 • 35 minutos para o fim da leitura

Este artigo descreve as ferramentas para criar Blazor aplicativos em várias plataformas.

1. Instale a versão mais recente do Visual Studio 2022 com a carga de trabalho
ASP.NET e desenvolvimento web .

2. Criar um novo projeto.

3. Para uma Blazor Server experiência, escolha o Blazor Server modelo de aplicativo,
que inclui código de demonstração e Bootstrap , ou o Blazor Server modelo App
Empty sem código de demonstração e Bootstrap. Selecione Avançar.

Para uma Blazor WebAssembly experiência, escolha o Blazor WebAssembly


modelo de aplicativo, que inclui código de demonstração e Bootstrap, ou o Blazor
WebAssembly modelo App Empty sem código de demonstração e Bootstrap.

4. Forneça um nome de projeto e confirme se o Local está correto. Selecione


Avançar.

5. Na caixa de diálogo Informações adicionais, marque a caixa de seleção ASP.NET


Core hospedada para um aplicativo hospedadoBlazor WebAssembly. Selecione
Criar.

Para obter informações sobre os dois Blazor modelos de hospedagem, Blazor


WebAssembly (autônomo e hospedado) e Blazor Server, consulte ASP.NET Core
Blazor modelos de hospedagem.

6. Pressione Ctrl + F5 (Windows) ou ⌘ + F5 (macOS) para executar o aplicativo.

Ao executar uma solução hospedada Blazor WebAssembly no Visual Studio, o


projeto de inicialização da solução é o Server projeto.

Para obter mais informações sobre como confiar no certificado de desenvolvimento


HTTPS ASP.NET Core, consulte Impor HTTPS em ASP.NET Core.

) Importante

Ao executar um aplicativo hospedado Blazor WebAssembly , execute o aplicativo


no projeto da Server solução.
Arquivo de solução do Visual Studio ( .sln )
Uma solução é um contêiner para organizar um ou mais projetos de código
relacionados. O Visual Studio e Visual Studio para Mac usar um arquivo de solução
( .sln ) para armazenar as configurações de uma solução. Os arquivos de solução usam
um formato exclusivo e não se destinam a serem editados diretamente.

As ferramentas fora do Visual Studio e Visual Studio para Mac podem interagir com
arquivos de solução:

A CLI do .NET pode criar arquivos de solução e listar/modificar os projetos em


arquivos de solução por meio do dotnet sln comando . Outros comandos da CLI
do .NET usam o caminho do arquivo de solução para vários comandos de
publicação, teste e empacotamento.
Visual Studio Code pode executar o dotnet sln comando e outros comandos da
CLI do .NET por meio de seu terminal integrado, mas não usa as configurações em
um arquivo de solução diretamente.

Em toda a documentação, a Blazorsolução é usada para descrever aplicativos criados a


Blazor WebAssembly partir do modelo de projeto com a opção ASP.NET Core hospedada
habilitada ou de um Blazor Hybrid modelo de projeto. Os aplicativos produzidos a partir
desses modelos de projeto incluem um arquivo de solução ( .sln ) por padrão. Para
aplicativos hospedados Blazor WebAssembly em que o desenvolvedor não está usando
o Visual Studio ou Visual Studio para Mac, o arquivo de solução pode ser ignorado ou
excluído se não for usado com comandos da CLI do .NET.

Para obter mais informações, consulte os seguintes recursos na documentação do Visual


Studio:

Introdução a projetos e soluções


O que são soluções e projetos no Visual Studio?

Usar Visual Studio Code para desenvolvimento


multiplataforma Blazor
Visual Studio Code é um IDE (Ambiente de Desenvolvimento Integrado) código
aberto e multiplataforma que pode ser usado para desenvolver Blazor aplicativos. Use a
CLI do .NET para criar um novo Blazor aplicativo para desenvolvimento com Visual
Studio Code. Para obter mais informações, consulte a versão do Linux deste artigo.

Blazor opções de modelo


A Blazor estrutura fornece modelos para criar novos aplicativos para cada um dos dois
Blazor modelos de hospedagem. Os modelos são usados para criar novos Blazor
projetos e soluções, independentemente das ferramentas selecionadas para Blazor
desenvolvimento (Visual Studio, Visual Studio para Mac, Visual Studio Code ou a CLI
(interface de linha de comando) do .NET):

Blazor Server modelos de projeto: blazorserver , blazorserver-empty


Blazor WebAssembly modelos de projeto: blazorwasm , blazorwasm-empty

Para obter mais informações sobre Blazoros modelos de hospedagem, consulte ASP.NET
Core Blazor modelos de hospedagem. Para obter mais informações sobre Blazor
modelos de projeto, consulte ASP.NET Core Blazor estrutura do projeto.

Para obter mais informações sobre opções de modelo, consulte os seguintes recursos:

Modelos padrão do .NET para o novo artigo do dotnet na documentação do .NET


Core:
blazorserver
blazorwasm
Passando a opção de ajuda ( -h ou --help ) para o comando da dotnet new CLI em
um shell de comando:
dotnet new blazorserver -h

dotnet new blazorwasm -h

Ferramentas de build do .NET WebAssembly


As ferramentas de build do .NET WebAssembly são baseadas no Emscripten , uma
cadeia de ferramentas do compilador para a plataforma Web. Para instalar as
ferramentas de build, use uma das seguintes abordagens:

Para o ASP.NET e a carga de trabalho de desenvolvimento da Web no instalador


do Visual Studio, selecione a opção ferramentas de build do .NET WebAssembly
na lista de componentes opcionais.
Execute dotnet workload install wasm-tools em um shell de comando
administrativo.

Quando a compilação AOT (ahead-of-time) é usada, há suporte para WebAssembly


Single Instruction, Multiple Data (SIMD), exceto para o Apple Safari no momento. Use
a <WasmEnableSIMD> propriedade no arquivo de projeto do aplicativo ( .csproj ) com um
valor de true :

XML
<PropertyGroup>
<WasmEnableSIMD>true</WasmEnableSIMD>
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>

Para habilitar o tratamento de exceção webAssembly, use a


<WasmEnableExceptionHandling> propriedade no arquivo de projeto do aplicativo

( .csproj ) com um valor de true :

XML

<PropertyGroup>
<WasmEnableExceptionHandling>true</WasmEnableExceptionHandling>
</PropertyGroup>

7 Observação

Ferramentas de build do .NET WebAssembly para projetos do .NET 6

A wasm-tools carga de trabalho instala as ferramentas de build para projetos do


.NET 7. No entanto, a versão do .NET 7 das ferramentas de build é incompatível
com projetos existentes criados com o .NET 6. Os projetos que usam as
ferramentas de build que devem dar suporte ao .NET 6 e ao .NET 7 devem usar
vários direcionamentos.

Use a wasm-tools-net6 carga de trabalho para projetos do .NET 6 ao desenvolver


aplicativos com o SDK do .NET 7. Para instalar a wasm-tools-net6 carga de trabalho,
execute o seguinte comando de um shell de comando administrativo:

CLI do .NET

dotnet workload install wasm-tools-net6

Para saber mais, consulte os recursos a seguir:

Compilação AOT (antecipada)


Revinculação de runtime
Blazor WebAssembly ASP.NET Core dependências nativas

Recursos adicionais
CLI (interface de linha de comando) do .NET
Suporte de Recarga Dinâmica do .NET para ASP.NET Core
Modelos de hospedagem do ASP.NET Core Blazor
Blazor estrutura do projeto ASP.NET Core
Tutoriais do ASP.NET Core Blazor Hybrid
Tutoriais de Blazor do ASP.NET Core
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Os seguintes tutoriais estão disponíveis sobre o Blazor no ASP.NET Core:

Criar seu primeiro aplicativo Blazor (Blazor Server)

Criar um aplicativo de lista de tarefas pendentes Blazor (Blazor Server ou Blazor


WebAssembly)

Usar ASP.NET Core SignalR com Blazor (Blazor Server ou Blazor WebAssembly)

Tutoriais do ASP.NET Core Blazor Hybrid

Módulos de aprendizagem

Para obter mais informações sobre os modelos de hospedagem Blazor, Blazor Server e
Blazor WebAssembly, consulte Modelos de hospedagem de Blazor do ASP.NET Core.
Criar um Blazor aplicativo de lista de
tarefas pendentes
Artigo • 28/11/2022 • 24 minutos para o fim da leitura

Este tutorial mostra como criar e modificar um Blazor aplicativo.

Saiba como:

" Criar um projeto de aplicativo de lista Blazor de tarefas pendentes


" Modificar Razor componentes
" Usar manipulação de eventos e associação de dados em componentes
" Usar o roteamento em um Blazor aplicativo

No final deste tutorial, você terá um aplicativo de lista de tarefas pendentes.

Pré-requisitos
SDK do .NET 7.0

Criar um Blazor aplicativo


Crie um novo Blazor aplicativo chamado TodoList em um shell de comando:

CLI do .NET

dotnet new blazorserver -o TodoList

O comando anterior cria uma pasta chamada TodoList com a opção -o|--output para
manter o aplicativo. A TodoList pasta é a pasta raiz do projeto. Altere os diretórios para
a TodoList pasta com o seguinte comando:

CLI do .NET

cd TodoList

Criar um aplicativo de lista Blazor de tarefas


pendentes
1. Adicione um novo Todo Razor componente ao aplicativo usando o seguinte
comando:

CLI do .NET

dotnet new razorcomponent -n Todo -o Pages

A -n|--name opção no comando anterior especifica o nome do novo Razor


componente. O componente é criado na pasta Pages do projeto com a opção -
o|--output .

) Importante

Razor os nomes de arquivo de componente exigem uma primeira letra


maiúscula. Abra a pasta Pages e confirme se o nome de arquivo do
componente Todo começa com uma letra maiúscula T . O nome de arquivo
será Todo.razor .

2. Abra o Todo componente em qualquer editor de arquivos e adicione uma


@page Razor diretiva à parte superior do arquivo com uma URL relativa de /todo .

Pages/Todo.razor :

razor

@page "/todo"

<PageTitle>Todo</PageTitle>

<h1>Todo</h1>

@code {

Salve o arquivo Pages/Todo.razor .

3. Adicione o componente Todo à barra de navegação.

O componente NavMenu é usado no layout do aplicativo. Layouts são componentes


que permitem evitar a duplicação de conteúdo em um aplicativo. O componente
NavLink fornece uma indicação na interface do usuário do aplicativo quando a

URL do componente é carregada pelo aplicativo.


No conteúdo do elemento de navegação ( <nav class="flex-column"> ) do NavMenu
componente, adicione o seguinte <div> elemento para o Todo componente.

Em Shared/NavMenu.razor :

razor

<div class="nav-item px-3">


<NavLink class="nav-link" href="todo">
<span class="oi oi-list-rich" aria-hidden="true"></span> Todo
</NavLink>
</div>

Salve o arquivo Shared/NavMenu.razor .

4. Compile e execute o aplicativo executando o dotnet watch run comando no shell


de comando da TodoList pasta . Depois que o aplicativo estiver em execução,
visite a nova página Todo selecionando o Todo link na barra de navegação do
aplicativo, que carrega a página em /todo .

Deixe o aplicativo executando o shell de comando. Sempre que um arquivo é


salvo, o aplicativo é recriado automaticamente e a página no navegador é
recarregada automaticamente.

5. Adicione um TodoItem.cs arquivo à raiz do projeto (a TodoList pasta) para manter


uma classe que representa um item de tarefas pendentes. Use o seguinte código
de C# para a classe TodoItem .

TodoItem.cs :

C#

public class TodoItem


{
public string? Title { get; set; }
public bool IsDone { get; set; }
}

7 Observação

Se estiver usando o Visual Studio para criar o arquivo e TodoItem a


TodoItem.cs classe, use uma das seguintes abordagens:

Remova o namespace gerado pelo Visual Studio para a classe .


Use o botão Copiar no bloco de código anterior e substitua todo o
conteúdo do arquivo gerado pelo Visual Studio.

6. Retorne ao Todo componente e execute as seguintes tarefas:

Adicione um campo para os itens de tarefas pendentes ao bloco @code . O


componente Todo usa esse campo para manter o estado da lista de tarefas
pendentes.
Adicione marcação da lista não ordenada e um loop foreach para renderizar
cada item de tarefa pendente como um item de lista ( <li> ).

Pages/Todo.razor :

razor

@page "/todo"

<PageTitle>Todo</PageTitle>

<h1>Todo</h1>

<ul>
@foreach (var todo in todos)
{
<li>@todo.Title</li>
}
</ul>

@code {
private List<TodoItem> todos = new();
}

7. O aplicativo requer elementos de interface do usuário para adicionar itens de


tarefas à lista. Adicione uma entrada de texto ( <input> ) e um botão ( <button> )
abaixo da lista não ordenada ( <ul>...</ul> ):

razor

@page "/todo"

<PageTitle>Todo</PageTitle>

<h1>Todo</h1>

<ul>
@foreach (var todo in todos)
{
<li>@todo.Title</li>
}
</ul>

<input placeholder="Something todo" />


<button>Add todo</button>

@code {
private List<TodoItem> todos = new();
}

8. Salve o TodoItem.cs arquivo e o arquivo atualizado Pages/Todo.razor . No shell de


comando, o aplicativo é recriado automaticamente quando os arquivos são salvos.
O navegador recarrega a página.

9. Nada acontece quando o botão Add todo é selecionado, porque nenhum


manipulador de eventos está anexado ao botão.

10. Adicione um método AddTodo ao componente Todo e registre o método para o


botão usando o atributo @onclick . O método C# AddTodo é chamado quando o
botão é selecionado:

razor

<input placeholder="Something todo" />


<button @onclick="AddTodo">Add todo</button>

@code {
private List<TodoItem> todos = new();

private void AddTodo()


{
// Todo: Add the todo
}
}

11. Para obter o título do novo item de tarefa pendente, adicione um campo de cadeia
de caracteres newTodo no início do bloco @code :

C#

private string? newTodo;

Modifique o elemento de texto <input> para associar newTodo ao @bind atributo :

razor
<input placeholder="Something todo" @bind="newTodo" />

12. Atualize o método AddTodo para adicionar o TodoItem com o título especificado à
lista. Limpe o valor da entrada de texto configurando newTodo para uma cadeia de
caracteres vazia:

razor

@page "/todo"

<PageTitle>Todo</PageTitle>

<h1>Todo</h1>

<ul>
@foreach (var todo in todos)
{
<li>@todo.Title</li>
}
</ul>

<input placeholder="Something todo" @bind="newTodo" />


<button @onclick="AddTodo">Add todo</button>

@code {
private List<TodoItem> todos = new();
private string? newTodo;

private void AddTodo()


{
if (!string.IsNullOrWhiteSpace(newTodo))
{
todos.Add(new TodoItem { Title = newTodo });
newTodo = string.Empty;
}
}
}

13. Salve o arquivo Pages/Todo.razor . O aplicativo é recriado automaticamente no


shell de comando e a página é recarregada no navegador.

14. O texto do título de cada item de tarefa pode se tornar editável, e uma caixa de
seleção pode ajudar o usuário a acompanhar os itens concluídos. Adicione uma
entrada de caixa de seleção para cada item de tarefa pendente e associe o valor
dele à propriedade IsDone . Altere @todo.Title para um elemento <input>
associado a todo.Title com @bind :

razor
<ul>
@foreach (var todo in todos)
{
<li>
<input type="checkbox" @bind="todo.IsDone" />
<input @bind="todo.Title" />
</li>
}
</ul>

15. Atualize o cabeçalho <h1> para mostrar uma contagem do número de itens de
tarefas pendentes que não foram concluídos ( IsDone é false ). A Razor expressão
no cabeçalho a seguir avalia cada vez que Blazor remete o componente.

razor

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

16. O componente concluído Todo ( Pages/Todo.razor ):

razor

@page "/todo"

<PageTitle>Todo</PageTitle>

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<ul>
@foreach (var todo in todos)
{
<li>
<input type="checkbox" @bind="todo.IsDone" />
<input @bind="todo.Title" />
</li>
}
</ul>

<input placeholder="Something todo" @bind="newTodo" />


<button @onclick="AddTodo">Add todo</button>

@code {
private List<TodoItem> todos = new();
private string? newTodo;

private void AddTodo()


{
if (!string.IsNullOrWhiteSpace(newTodo))
{
todos.Add(new TodoItem { Title = newTodo });
newTodo = string.Empty;
}
}
}

17. Salve o arquivo Pages/Todo.razor . O aplicativo é recriado automaticamente no


shell de comando e a página é recarregada no navegador.

18. Adicione, edite e marque itens de tarefas pendentes como concluídos para testar o
componente.

19. Quando terminar, desligue o aplicativo no shell de comando. Muitos shells de


comando aceitam o comando de teclado Ctrl + C (Windows) ou ⌘ + C (macOS)
para interromper um aplicativo.

Publicar no Azure
Para obter informações sobre como implantar no Azure, confira Início Rápido: Implantar
um aplicativo Web ASP.NET.

Próximas etapas
Neste tutorial, você aprendeu a:

" Criar um projeto de aplicativo de lista Blazor de tarefas pendentes


" Modificar Razor componentes
" Usar manipulação de eventos e associação de dados em componentes
" Usar o roteamento em um Blazor aplicativo

Saiba mais sobre ferramentas para ASP.NET Core Blazor:

Ferramentas para ASP.NET Core Blazor


Usar ASP.NET Core SignalR com Blazor
Artigo • 28/11/2022 • 68 minutos para o fim da leitura

Este tutorial ensina os conceitos básicos da criação de um aplicativo em tempo real


usando SignalR com Blazoro .

Saiba como:

" Criar um Blazor projeto


" Adicionar a SignalR biblioteca de clientes
" Adicionar um SignalR hub
" Adicionar SignalR serviços e um ponto de extremidade para o SignalR hub
" Adicionar Razor código de componente para chat

No final deste tutorial, você terá um aplicativo de chat funcional.

Pré-requisitos
Visual Studio

Visual Studio 2022 ou posterior com a carga de trabalho ASP.NET e


desenvolvimento para a Web
SDK do .NET 6.0

Aplicativo de exemplo
O download do aplicativo de chat de exemplo do tutorial não é necessário para este
tutorial. O aplicativo de exemplo é o aplicativo final e funcional produzido seguindo as
etapas deste tutorial.

Exibir ou baixar o código de exemplo

Criar um Blazor Server aplicativo


Siga as diretrizes para sua escolha de ferramentas:

Visual Studio
7 Observação

O Visual Studio 2022 ou posterior e o SDK do .NET Core 6.0.0 ou posterior são
necessários.

1. Criar um novo projeto.

2. Selecione o Blazor Server Modelo de aplicativo. Selecione Avançar.

3. Digite BlazorServerSignalRApp o campo Nome do projeto. Confirme se a


entrada Local está correta ou forneça um local para o projeto. Selecione
Avançar.

4. Selecione Criar.

Adicionar a SignalR biblioteca de clientes


Visual Studio

1. Em Gerenciador de Soluções, clique com o botão direito do mouse no


BlazorServerSignalRApp projeto e selecione Gerenciar Pacotes NuGet.

2. Na caixa de diálogo Gerenciar Pacotes NuGet, confirme se a origem do


pacote está definida como nuget.org .

3. Com Procurar selecionado, digite Microsoft.AspNetCore.SignalR.Client na


caixa de pesquisa.

4. Nos resultados da pesquisa, selecione o


Microsoft.AspNetCore.SignalR.Client pacote. Defina a versão para
corresponder à estrutura compartilhada do aplicativo. Selecione Instalar.

5. Se a caixa de diálogo Visualizar Alterações for exibida, selecione OK.

6. Se a caixa de diálogo Aceitação da Licença for exibida, selecione Aceito se


você concordar com os termos de licença.

Adicionar um SignalR hub


Crie uma Hubs pasta (plural) e adicione a seguinte ChatHub classe ( Hubs/ChatHub.cs ):

C#

using Microsoft.AspNetCore.SignalR;

namespace BlazorServerSignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}

Adicionar serviços e um ponto de extremidade


para o SignalR hub
1. Abra o arquivo Program.cs .

2. Adicione os namespaces para Microsoft.AspNetCore.ResponseCompression e a


ChatHub classe à parte superior do arquivo:

C#

using Microsoft.AspNetCore.ResponseCompression;
using BlazorServerSignalRApp.Server.Hubs;

3. Adicione serviços middleware de compactação de resposta a Program.cs :

C#

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});

4. Em Program.cs :
Use o Middleware de Compactação de Resposta na parte superior da
configuração do pipeline de processamento.
Entre os pontos de extremidade para mapear o Blazor hub e o fallback do
lado do cliente, adicione um ponto de extremidade para o hub.

C#

app.UseResponseCompression();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToPage("/_Host");

app.Run();

Adicionar Razor código de componente para


chat
1. Abra o arquivo Pages/Index.razor .

2. Substitua a marcação pelo seguinte código:

razor

@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable

<PageTitle>Index</PageTitle>

<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>

@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();

hubConnection.On<string, string>("ReceiveMessage", (user,


message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});

await hubConnection.StartAsync();
}

private async Task Send()


{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput,
messageInput);
}
}

public bool IsConnected =>


hubConnection?.State == HubConnectionState.Connected;

public async ValueTask DisposeAsync()


{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}

7 Observação

Desabilite o middleware de compactação de resposta no Development ambiente ao


usar Recarga Dinâmica. Para obter mais informações, consulte ASP.NET Core
BlazorSignalR diretrizes.

Executar o aplicativo
Siga as diretrizes para suas ferramentas:

Visual Studio

1. Pressione F5 para executar o aplicativo com depuração ou Ctrl + F5

(Windows)/ ⌘ + F5 (macOS) para executar o aplicativo sem depuração.

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

3. Escolha qualquer navegador, insira um nome e uma mensagem e selecione o


botão para enviar a mensagem. O nome e a mensagem são exibidos em
ambas as páginas instantaneamente:

Citações: Star Trek VI: O País ©Desconhecido 1991 Paramount


Próximas etapas
Neste tutorial, você aprendeu a:

" Criar um Blazor projeto


" Adicionar a SignalR biblioteca de clientes
" Adicionar um SignalR hub
" Adicionar SignalR serviços e um ponto de extremidade para o SignalR hub
" Adicionar Razor código de componente para chat

Para saber mais sobre como criar Blazor aplicativos, consulte a Blazor documentação:

BlazorASP.NET Core

autenticação de token de portador com Identity Eventos de Servidor, WebSockets


e Server-Sent

Recursos adicionais
Hubs seguros SignalR para aplicativos hospedados Blazor WebAssembly
Visão geral do ASP.NET Core SignalR
SignalR negociação entre origens para autenticação
SignalR Configuração
Depurar ASP.NET Core Blazor WebAssembly
Diretrizes de mitigação de ameaças para ASP.NET Core Blazor Server
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Modelos de hospedagem do ASP.NET
Core Blazor
Artigo • 28/11/2022 • 35 minutos para o fim da leitura

Este artigo explica os diferentes Blazor modelos de hospedagem e como escolher qual
deles usar.

Blazor é uma estrutura da Web para a criação de componentes da interface do usuário


da Web (Razor componentes) que podem ser hospedados de maneiras diferentes.
Razoros componentes podem executar o lado do servidor em ASP.NET Core (Blazor
Server) versus no lado do cliente no navegador em um runtime do .NET baseado em
WebAssembly (Blazor WebAssembly, Blazor WASM). Você também pode hospedar
Razor componentes em aplicativos móveis e de área de trabalho nativos que são
renderizados em um controle inserido Web View (Blazor Hybrid). Independentemente
do modelo de hospedagem, a maneira como você cria Razor componentes é a mesma.
Os mesmos Razor componentes podem ser usados com qualquer um dos modelos de
hospedagem inalterados.

Blazor Server
Com o Blazor Server modelo de hospedagem, o aplicativo é executado no servidor de
dentro de um aplicativo ASP.NET Core. Atualizações de interface do usuário,
manipulação de eventos e chamadas JavaScript são tratadas em uma SignalR conexão
usando o protocolo WebSockets. O estado no servidor associado a cada cliente
conectado é chamado de circuito. Os circuitos não estão vinculados a uma conexão de
rede específica e podem tolerar interrupções de rede temporárias e tentativas do cliente
de se reconectar ao servidor quando a conexão for perdida.

Em um aplicativo renderizado pelo servidor tradicional, abrir o mesmo aplicativo em


várias telas do navegador (guias ou iframes ) normalmente não se traduz em demandas
de recursos adicionais no servidor. Em um Blazor Server aplicativo, cada tela do
navegador requer um circuito separado e instâncias separadas do estado do
componente gerenciado pelo servidor. Blazor considera fechar uma guia do navegador
ou navegar para uma URL externa uma terminação normal . No caso de um
encerramento normal, o circuito e os recursos associados são liberados imediatamente.
Um cliente também pode se desconectar normalmente, por exemplo, devido a uma
interrupção de rede. Blazor Server armazena circuitos desconectados para um intervalo
configurável para permitir que o cliente se reconecte.
No cliente, o Blazor script ( blazor.server.js ) estabelece a SignalR conexão com o
servidor. O script é fornecido ao aplicativo do lado do cliente de um recurso inserido no
ASP.NET Core estrutura compartilhada. O aplicativo do lado do cliente é responsável por
persistir e restaurar o estado do aplicativo conforme necessário.

O Blazor Server modelo de hospedagem oferece vários benefícios:

O tamanho do download é significativamente menor do que um Blazor


WebAssembly aplicativo e o aplicativo é carregado muito mais rapidamente.
O aplicativo aproveita ao máximo os recursos do servidor, incluindo o uso de APIs
do .NET Core.
O .NET Core no servidor é usado para executar o aplicativo, portanto, as
ferramentas existentes do .NET, como depuração, funcionam conforme o
esperado.
Há suporte para clientes finos. Por exemplo, Blazor Server os aplicativos funcionam
com navegadores que não dão suporte a WebAssembly e em dispositivos com
restrição de recursos.
A base de código .NET/C# do aplicativo, incluindo o código de componente do
aplicativo, não é servida aos clientes.

O Blazor Server modelo de hospedagem tem as seguintes limitações:

A latência mais alta geralmente existe. Cada interação do usuário envolve um salto
de rede.
Não há suporte offline. Se a conexão do cliente falhar, o aplicativo deixará de
funcionar.
O dimensionamento de aplicativos com muitos usuários requer recursos de
servidor para lidar com várias conexões de cliente e o estado do cliente.
Um servidor ASP.NET Core é necessário para atender ao aplicativo. Cenários de
implantação sem servidor não são possíveis, como servir o aplicativo de uma CDN
(Rede de Distribuição de Conteúdo).

É recomendável usar o Serviço do Azure SignalR para Blazor Server aplicativos. O serviço
permite escalar verticalmente um Blazor Server aplicativo para um grande número de
conexões simultâneas SignalR .

Blazor WebAssembly
Blazor WebAssembly Os aplicativos (WASM) executam o lado do cliente no navegador
em um runtime do .NET baseado em WebAssembly. O Blazor aplicativo, suas
dependências e o runtime do .NET são baixados para o navegador. O aplicativo é
executado diretamente no thread da interface do usuário do navegador. As atualizações
da interface do usuário e o tratamento de eventos ocorrem no mesmo processo. Os
ativos do aplicativo são implantados como arquivos estáticos em um servidor Web ou
serviço capaz de fornecer conteúdo estático aos clientes.
Quando o Blazor WebAssembly aplicativo é criado para implantação sem um back-end
ASP.NET Core aplicativo para atender seus arquivos, o aplicativo é chamado de
aplicativo autônomoBlazor WebAssembly. Quando o aplicativo é criado para
implantação com um aplicativo de back-end para atender seus arquivos, o aplicativo é
chamado de aplicativo hospedadoBlazor WebAssembly .

Um Blazor WebAssembly aplicativo criado como um PWA (Aplicativo Web Progressivo)


usa APIs de navegador modernas para habilitar muitos dos recursos de um aplicativo
cliente nativo, como trabalhar offline, em execução em sua própria janela de aplicativo,
iniciar do sistema operacional do host, receber notificações por push e atualizar
automaticamente em segundo plano.

Usando o hospedado Blazor WebAssembly, você obtém uma experiência de


desenvolvimento da Web de pilha completa com o .NET, incluindo a capacidade de
compartilhar código entre os aplicativos cliente e servidor, suporte para pré-geração e
integração com MVC e Razor Pages. Um aplicativo cliente hospedado pode interagir
com seu aplicativo de servidor de back-end pela rede usando uma variedade de
estruturas e protocolos de mensagens, como API Web, gRPC-web e SignalR (Use
ASP.NET Core SignalR com Blazor).

O blazor.webassembly.js script é fornecido pela estrutura e manipula:

Baixar o runtime do .NET, o aplicativo e as dependências do aplicativo.


Inicialização do runtime para executar o aplicativo.

O Blazor WebAssembly modelo de hospedagem (WASM) oferece vários benefícios:

Não há nenhuma dependência do lado do servidor do .NET depois que o


aplicativo é baixado do servidor, portanto, o aplicativo permanece funcional se o
servidor ficar offline.
Os recursos e capacidades do cliente são totalmente aproveitados.
O trabalho é descarregado do servidor para o cliente.
Um servidor Web ASP.NET Core não é necessário para hospedar o aplicativo.
Cenários de implantação sem servidor são possíveis, como servir o aplicativo de
uma CDN (Rede de Distribuição de Conteúdo).

O Blazor WebAssembly modelo de hospedagem tem as seguintes limitações:

O aplicativo é restrito aos recursos do navegador.


Hardware e software cliente compatíveis (por exemplo, suporte a WebAssembly)
são necessários.
O tamanho do download é maior e os aplicativos levam mais tempo para serem
carregados.
Blazor WebAssembly dá suporte à compilação AOT (antecipada), na qual você pode
compilar seu código .NET diretamente no WebAssembly. A compilação AOT resulta em
melhorias de desempenho de runtime em detrimento de um tamanho de aplicativo
maior. Para obter mais informações, consulte Hospedar e implantar ASP.NET Core Blazor
WebAssembly.

As mesmas ferramentas de build do .NET WebAssembly usadas para compilação AOT


também revinculam o runtime do WebAssembly do .NET para cortar o código de
runtime não utilizado.

Blazor WebAssembly inclui suporte para cortar código não utilizado de bibliotecas de
estrutura do .NET Core. Para obter mais informações, consulte ASP.NET Core Blazor
globalização e localização. O compilador .NET pré-compacta ainda mais um Blazor
WebAssembly aplicativo para um conteúdo de aplicativo menor.

Blazor WebAssembly os aplicativos podem usar dependências nativas criadas para


serem executadas no WebAssembly.

Blazor Hybrid
Blazor também pode ser usado para criar aplicativos cliente nativos usando uma
abordagem híbrida. Aplicativos híbridos são aplicativos nativos que aproveitam as
tecnologias da Web para sua funcionalidade. Em um Blazor Hybrid aplicativo, Razor os
componentes são executados diretamente no aplicativo nativo (não no WebAssembly)
juntamente com qualquer outro código .NET e renderizam a interface do usuário da
Web com base em HTML e CSS em um controle inserido Web View por meio de um
canal de interoperabilidade local.
Blazor Hybridos aplicativos podem ser criados usando diferentes estruturas de
aplicativos nativos do .NET, incluindo .NET MAUI, WPF e Windows Forms. Blazor fornece
BlazorWebView controles para adicionar Razor componentes a aplicativos criados com

essas estruturas. Usar Blazor com .NET MAUI oferece uma maneira conveniente de criar
aplicativos multiplataforma Blazor Hybrid para dispositivos móveis e desktop, enquanto
Blazor a integração com o WPF e Windows Forms pode ser uma ótima maneira de
modernizar aplicativos existentes.

Como Blazor Hybrid os aplicativos são aplicativos nativos, eles podem dar suporte a
funcionalidades que não estão disponíveis apenas com a plataforma Web. Blazor Hybrid
os aplicativos têm acesso completo aos recursos da plataforma nativa por meio de APIs
normais do .NET. Blazor Hybridos aplicativos também podem compartilhar e reutilizar
componentes com aplicativos ou Blazor WebAssembly existentesBlazor Server. Blazor
Hybrid Os aplicativos combinam os benefícios da Web, dos aplicativos nativos e da
plataforma .NET.

O Blazor Hybrid modelo de hospedagem oferece vários benefícios:

Reutilize os componentes existentes que podem ser compartilhados entre


dispositivos móveis, desktop e Web.
Aproveite as habilidades, a experiência e os recursos de desenvolvimento para a
Web.
Os aplicativos têm acesso completo aos recursos nativos do dispositivo.

O Blazor Hybrid modelo de hospedagem tem as seguintes limitações:

Aplicativos cliente nativos separados devem ser criados, implantados e mantidos


para cada plataforma de destino.
Os aplicativos cliente nativos geralmente levam mais tempo para localizar, baixar e
instalar do que acessar um aplicativo Web em um navegador.

Para obter mais informações, consulte ASP.NET Core Blazor Hybrid.

Para obter mais informações sobre estruturas de cliente nativas da Microsoft, consulte
os seguintes recursos:

.NET Multi-platform App UI (.NET MAUI)


WPF (Windows Presentation Foundation)
Windows Forms

Qual Blazor modelo de hospedagem devo


escolher?
Selecione o Blazor modelo de hospedagem com base nos requisitos de recursos do
aplicativo. A tabela a seguir mostra as principais considerações para selecionar o
modelo de hospedagem.

Blazor Hybridos aplicativos incluem .NET MAUI, WPF e Windows Forms aplicativos de
estrutura.

Recurso Blazor Blazor Blazor


Server WebAssembly Hybrid
(WASM)

Compatibilidade completa da API do .NET ✔️ ❌ ✔️

Acesso direto aos recursos de servidor e rede ✔️ ❌† ❌†

Tamanho de carga pequeno com tempo de ✔️ ❌ ❌


carregamento inicial rápido

Velocidade de execução quase nativa ✔️ ✔️‡ ✔️

Código do aplicativo seguro e privado no ✔️ ❌† ❌†


servidor

Executar aplicativos offline depois de baixados ❌ ✔️ ✔️

Hospedagem de site estático ❌ ✔️ ❌

Descarrega o processamento para clientes ❌ ✔️ ✔️

Acesso completo aos recursos do cliente nativo ❌ ❌ ✔️

Implantação baseada na Web ✔️ ✔️ ❌

Blazor WebAssembly† e Blazor Hybrid aplicativos podem usar APIs baseadas em servidor
para acessar recursos de servidor/rede e acessar código de aplicativo privado e seguro.
Blazor WebAssembly‡ atinge apenas o desempenho quase nativo com compilação AOT
(antecipada).

Depois de escolher o modelo de hospedagem do aplicativo, você pode gerar um Blazor


Server aplicativo ou Blazor WebAssembly de um Blazor modelo de projeto. Para obter
mais informações, consulte Ferramentas para ASP.NET Core Blazor.

Para criar um Blazor Hybrid aplicativo, consulte os artigos em ASP.NET Core Blazor
Hybrid tutoriais.

Compatibilidade completa da API do .NET


Blazor Server os aplicativos e Blazor Hybrid têm compatibilidade completa com a API do
.NET, enquanto Blazor WebAssembly os aplicativos são limitados a um subconjunto de
APIs do .NET. Quando a especificação de um aplicativo exigir uma ou mais APIs do .NET
que não estão disponíveis para Blazor WebAssembly aplicativos, escolha Blazor Server
ou Blazor Hybrid.

Acesso direto aos recursos de servidor e rede


Blazor Server os aplicativos têm acesso direto aos recursos de servidor e rede em que o
aplicativo está sendo executado. Como Blazor WebAssembly os aplicativos e Blazor
Hybrid são executados em um cliente, eles não têm acesso direto aos recursos de
servidor e rede. Blazor WebAssembly os aplicativos e Blazor Hybrid podem acessar
recursos de servidor e rede indiretamente por meio de APIs protegidas baseadas em
servidor. APIs baseadas em servidor podem estar disponíveis por meio de bibliotecas,
pacotes e serviços de terceiros. Leve em conta as seguintes considerações:

Bibliotecas, pacotes e serviços de terceiros podem ser caros para implementar e


manter, com suporte fraco ou introduzir riscos de segurança.
Se uma ou mais APIs baseadas em servidor forem desenvolvidas internamente
pela sua organização, recursos adicionais serão necessários para compilá-las e
mantê-las.

Para evitar APIs baseadas em servidor para Blazor WebAssembly aplicativos ou Blazor
Hybrid , adote Blazor Server, que pode acessar recursos de servidor e rede diretamente.

Tamanho de carga pequeno com tempo de carregamento


inicial rápido
Blazor Server os aplicativos têm tamanhos de carga relativamente pequenos com
tempos de carregamento iniciais mais rápidos. Quando um tempo de carregamento
inicial rápido é desejado, adote Blazor Server.

Velocidade de execução quase nativa


Blazor Server os aplicativos geralmente são executados no servidor rapidamente. No
entanto, Blazor Server os aplicativos geralmente são mais lentos do que outros tipos de
aplicativos executados nativamente no cliente.

Blazor Hybrid os aplicativos são executados usando o runtime do .NET nativamente na


plataforma de destino, o que oferece a melhor velocidade possível.
Blazor WebAssembly, incluindo PWAs (Aplicativos Web Progressivos), os aplicativos são
executados usando o runtime do .NET para WebAssembly, que é mais lento do que ser
executado diretamente na plataforma, mesmo para aplicativos que são compilados
antecipadamente (AOT) para WebAssembly no navegador.

Código do aplicativo seguro e privado no servidor


Manter o código do aplicativo de forma segura e privada no servidor é um recurso
interno do Blazor Server. Blazor WebAssembly os aplicativos e Blazor Hybrid podem
usar APIs baseadas em servidor para acessar a funcionalidade que deve ser mantida
privada e segura. As considerações para desenvolver e manter APIs baseadas em
servidor descritas na seção Acesso direto aos recursos de servidor e rede se aplicam. Se
o desenvolvimento e a manutenção de APIs baseadas em servidor não forem desejáveis
para manter o código de aplicativo seguro e privado, adote o Blazor Server modelo de
hospedagem.

Executar aplicativos offline depois de baixados


Blazor WebAssemblyaplicativos criados como PWAs (Aplicativos Web Progressivos) e
Blazor Hybrid aplicativos podem ser executados offline, o que é particularmente útil
quando os clientes não conseguem se conectar à Internet. Blazor Server os aplicativos
não são executados quando a conexão com o servidor é perdida. Se um aplicativo
precisar ser executado offline Blazor WebAssembly e Blazor Hybrid for a melhor opção.

Hospedagem de site estático


A hospedagem de site estático é possível com Blazor WebAssembly aplicativos porque
eles são baixados para clientes como um conjunto de arquivos estáticos. Blazor
WebAssembly os aplicativos não exigem um servidor para executar o código do lado do
servidor para baixar e executar. Blazor WebAssembly os aplicativos podem ser
entregues por meio de uma CDN (Rede de Distribuição de Conteúdo) (por exemplo,
CDN do Azure ). Embora Blazor Hybrid os aplicativos sejam compilados em um ou
mais ativos de implantação autocontidos, os ativos geralmente são fornecidos aos
clientes por meio de uma loja de aplicativos de terceiros. Se a hospedagem estática for
um requisito do aplicativo, selecione Blazor WebAssembly.

Descarrega o processamento para clientes


Blazor WebAssembly e Blazor Hybrid aplicativos são executados em clientes e, portanto,
descarregam o processamento para clientes. Blazor Server os aplicativos são executados
em um servidor, de modo que a demanda de recursos do servidor normalmente
aumenta com o número de usuários e a quantidade de processamento necessária por
usuário. Quando é possível descarregar a maioria ou todo o processamento de um
aplicativo para clientes e o aplicativo processa uma quantidade significativa de dados
Blazor WebAssembly ou Blazor Hybrid é a melhor opção.

Acesso completo aos recursos do cliente nativo


Blazor Hybrid os aplicativos têm acesso completo aos recursos nativos da API do cliente
por meio de estruturas de aplicativo nativas do .NET. Em Blazor Hybrid aplicativos, Razor
os componentes são executados diretamente no aplicativo nativo, não no
WebAssembly . Quando os recursos completos do cliente são um requisito, Blazor
Hybrid é a melhor opção.

Implantação baseada na Web


Blazor Server e Blazor WebAssembly são implantados como aplicativos Web que são
atualizados na próxima atualização do aplicativo.

Blazor Hybrid os aplicativos são aplicativos cliente nativos que normalmente exigem um
instalador e um mecanismo de implantação específico da plataforma.

Recursos adicionais
ASP.NET Core Blazor Hybrid
Ferramentas para ASP.NET Core Blazor
Blazor estrutura do projeto ASP.NET Core
Visão geral do ASP.NET Core SignalR
diretrizes BlazorSignalR de ASP.NET Core
Usar ASP.NET Core SignalR com Blazor
ASP.NET Core Blazor Hybrid
Artigo • 21/12/2022 • 3 minutos para o fim da leitura

Este artigo explica o Blazor Hybrid no ASP.NET Core, uma maneira de criar a IU da Web
interativa do lado do cliente com o .NET em um aplicativo ASP.NET Core.

Use Blazor Hybrid para combinar estruturas de cliente nativas de desktop e móveis com
.NET e Blazor.

Em um aplicativo Blazor Hybrid, os componentes Razor são executados nativamente no


dispositivo. Os componentes são renderizados em um controle Web View inserido por
meio de um canal de interoperabilidade local. Os componentes não são executados no
navegador e WebAssembly não é envolvido. Os componentes Razor carregam e
executam o código rapidamente, e os componentes têm acesso total aos recursos
nativos do dispositivo por meio da plataforma .NET. Os estilos de componente
renderizados em uma Web View são dependentes da plataforma e podem exigir que
você dê conta das diferenças de renderização entre plataformas usando folhas de estilo
personalizadas.

Os artigos Blazor Hybrid abordam assuntos relativos à integração de componentes


Razor em estruturas de cliente nativas.

Aplicativos Blazor Hybrid com .NET MAUI


O suporte para Blazor Hybrid é integrado à estrutura .NET Multi-platform App UI (.NET
MAUI). .NET MAUI inclui o controle BlazorWebView que permite renderizar
componentes Razor em um Web View inserido. Ao usar o .NET MAUI e o Blazor juntos,
você pode reutilizar um conjunto de componentes de interface do usuário da Web em
dispositivos móveis, desktops e Web.

Aplicativos Blazor Hybrid com WPF e Windows


Forms
Aplicativos Blazor Hybrid podem ser criados com a Windows Presentation Foundation
(WPF) e o Windows Forms. Blazorfornece BlazorWebView controles para ambas as
estruturas (WPF BlazorWebView, Windows Forms BlazorWebView). Os componentes
Razor são executados nativamente na área de trabalho do Windows e renderizados em
um Web View inserido. Usar Blazor na WPF e no Windows Forms permite adicionar uma
nova interface do usuário aos aplicativos da área de trabalho existentes do Windows
que podem ser reutilizados em plataformas com .NET MAUI ou na Web.

Configuração de Web View


Blazor Hybrid expõe a configuração Web View subjacente para diferentes plataformas
por meio de eventos do controle BlazorWebView :

BlazorWebViewInitializing fornece acesso às configurações usadas para criar o


Web View em cada plataforma, se as configurações estiverem disponíveis.
BlazorWebViewInitialized fornece acesso ao Web View para permitir a definição

adicional das configurações.

Use os padrões preferenciais em cada plataforma para anexar manipuladores de


eventos aos eventos para executar o código personalizado.

Documentação da API:

.NET MAUI
BlazorWebViewInitializing
BlazorWebViewInitialized
WPF
BlazorWebViewInitializing
BlazorWebViewInitialized
Windows Forms
BlazorWebViewInitializing
BlazorWebViewInitialized

Exceções sem tratamento em aplicativos


Windows Forms e WPF
Esta seção só se aplica a aplicativos Windows Forms e WPFBlazor Hybrid.

Crie um retorno de chamada para UnhandledException na


System.AppDomain.CurrentDomain propriedade . O exemplo a seguir usa uma diretiva
do compilador para exibir um MessageBox que alerta o usuário de que ocorreu um erro
ou mostra as informações de erro para o desenvolvedor. Registre as informações de
erro em error.ExceptionObject .

C#
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
{
#if DEBUG
MessageBox.Show(text: error.ExceptionObject.ToString(), caption:
"Error");
#else
MessageBox.Show(text: "An error has occurred.", caption: "Error");
#endif

// Log the error information (error.ExceptionObject)


};

Globalização e localização
Esta seção só se aplica a .NET MAUIBlazor Hybrid aplicativos.

.NET MAUI configura o CurrentCulture e CurrentUICulture com base nas informações de


ambiente do dispositivo.

IStringLocalizer e outraS APIs no namespace geralmente funcionam conforme o


Microsoft.Extensions.Localization esperado, juntamente com a formatação, análise e
associação de globalização que dependem da cultura do usuário.

Ao alterar dinamicamente a cultura do aplicativo em runtime, o aplicativo deve ser


recarregado para refletir a alteração na cultura, que cuida da re-geração do
componente raiz e da passagem da nova cultura para componentes filho rerender.

. O sistema de recursos do NET dá suporte à inserção de imagens localizadas (como


blobs) em um aplicativo, mas Blazor Hybrid não pode exibir as imagens inseridas nos
Razor componentes no momento. Mesmo que um usuário leia os bytes de uma imagem
em um Stream usando ResourceManager, a estrutura não dá suporte atualmente à
renderização da imagem recuperada em um Razor componente.

Uma abordagem específica da plataforma para incluir imagens localizadas é um recurso


do . O sistema de recursos do NET, mas os elementos do navegador de um Razor
componente em um .NET MAUIBlazor Hybrid aplicativo não são capazes de interagir
com essas imagens.

Para saber mais, consulte os recursos a seguir:

Localização de imagens e cadeia de caracteres do Xamarin.Forms: as diretrizes


geralmente se aplicam aos Blazor Hybrid aplicativos. Nem todos os cenários têm
suporte no momento.
Blazor Componente de imagem para exibir imagens que não são acessíveis por
meio de pontos de extremidade HTTP (dotnet/aspnetcore #25274)

Recursos adicionais
Tutoriais de Blazor Hybrid no ASP.NET Core
.NET Multi-platform App UI (.NET MAUI)
WPF (Windows Presentation Foundation)
Windows Forms
Tutoriais de Blazor Hybrid do ASP.NET
Core
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Criar um aplicativo .NET MAUIBlazor

Criar um aplicativo Blazor do Windows Forms

Criar um aplicativo Blazor da WPF (Windows Presentation Foundation)

Para obter mais informações sobre como hospedar modelos, consulte Hospedagem de
modelos de Blazor no ASP.NET Core.
Criar um aplicativo .NET MAUIBlazor
Artigo • 28/11/2022 • 7 minutos para o fim da leitura

Saiba mais sobre Blazor Hybrid aplicativos:

ASP.NET Core Blazor Hybrid


Criar um aplicativo Blazor do Windows
Forms
Artigo • 24/12/2022 • 4 minutos para o fim da leitura

Este tutorial mostra como criar e executar um aplicativo Windows FormsBlazor. Você
aprenderá como:

" Criar um projeto de aplicativo Windows Forms Blazor


" Executar o aplicativo no Windows

Pré-requisitos
Plataformas com suporte (documentação Windows Forms)
Visual Studio 2022 com a carga de trabalho de desenvolvimento da área de
trabalho do .NET

Carga de trabalho do Visual Studio


Se a carga de trabalho de desenvolvimento da área de trabalho do .NET não estiver
instalada, use o instalador do Visual Studio para instalar a carga de trabalho. Para obter
mais informações, consulte Modificar cargas de trabalho, componentes e pacotes de
idiomas do Visual Studio.

Criar um projeto de Windows Forms Blazor


Inicie o Visual Studio. Na Janela Iniciar, selecione Criar um novo projeto:
Na caixa de diálogo Criar um novo projeto , filtre a lista suspensa Tipo de projeto para
Área de Trabalho. Selecione o modelo de projeto C# para Windows Forms Aplicativo e
selecione o botão Avançar:

Na caixa de diálogo Configurar seu novo projeto :

Defina o nome do projeto como WinFormsBlazor.


Escolha um local adequado para o projeto.
Selecione o botão Avançar.

Na caixa de diálogo Informações adicionais , selecione a versão da estrutura com a lista


suspensa Estrutura . Selecione o botão Criar :
Use o Gerenciador de Pacotes NuGet para instalar o
Microsoft.AspNetCore.Components.WebView.WindowsForms pacote NuGet:

Em Gerenciador de Soluções, clique com o botão direito do mouse no nome do projeto


e WinFormsBlazorselecione Editar Arquivo de Projeto para abrir o arquivo de projeto
( WinFormsBlazor.csproj ).

Na parte superior do arquivo de projeto, altere o SDK para Microsoft.NET.Sdk.Razor :

XML

<Project Sdk="Microsoft.NET.Sdk.Razor">

Salve as alterações no arquivo de projeto ( WinFormsBlazor.csproj ).

Adicione um _Imports.razor arquivo à raiz do projeto com uma @using diretiva para
Microsoft.AspNetCore.Components.Web.

_Imports.razor :

razor

@using Microsoft.AspNetCore.Components.Web

Salve o arquivo _Imports.razor .

Adicione uma wwwroot pasta ao projeto.

Adicione um index.html arquivo à wwwroot pasta com a marcação a seguir.

wwwroot/index.html :

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WinFormsBlazor</title>
<base href="/" />
<link href="css/app.css" rel="stylesheet" />
<link href="WinFormsBlazor.styles.css" rel="stylesheet" />
</head>

<body>

<div id="app">Loading...</div>

<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

<script src="_framework/blazor.webview.js"></script>

</body>

</html>

Dentro da wwwroot pasta, crie uma css pasta para armazenar folhas de estilos.

Adicione uma app.css folha de estilos à wwwroot/css pasta com o conteúdo a seguir.

wwwroot/css/app.css :

css

html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

h1:focus {
outline: none;
}

a, .btn-link {
color: #0071c1;
}

.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}

.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}

.validation-message {
color: red;
}

#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}

#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

Adicione o componente a seguir Counter à raiz do projeto, que é o componente padrão


Counter encontrado em Blazor modelos de projeto.

Counter.razor :

razor

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Salve o Counter componente ( Counter.razor ).


Em Gerenciador de Soluções, clique duas vezes no Form1.cs arquivo para abrir o
designer:

Abra a Caixa de Ferramentas selecionando o botão Caixa de Ferramentas ao longo da


borda esquerda da janela do Visual Studio ou selecionando o comando de menu
Exibir>Caixa de Ferramentas .

Localize o BlazorWebView controle em


Microsoft.AspNetCore.Components.WebView.WindowsForms . Arraste o BlazorWebView da

Caixa de Ferramentas para o Form1 designer. Tenha cuidado para não arrastar
acidentalmente um WebView2 controle para o formulário.

O Visual Studio mostra o BlazorWebView controle no designer de formulários como


WebView2 e nomeia automaticamente o controle blazorWebView1 :
Em Form1 , selecione o BlazorWebView ( WebView2 ) com um único clique.

BlazorWebViewNas Propriedades do , confirme se o controle é chamado


blazorWebView1 . Se o nome não blazorWebView1 for , o controle errado foi arrastado da
Caixa de Ferramentas. Exclua o WebView2 controle em Form1 e arraste o BlazorWebView
controle para o formulário.

Nas propriedades do controle, altere o BlazorWebViewvalor do Dock para


Preenchimento:
No designer, clique com o botão direito do Form1 mouse Form1 e selecione Exibir
Código.

Adicione namespaces para Microsoft.AspNetCore.Components.WebView.WindowsForms


e Microsoft.Extensions.DependencyInjection à parte superior do Form1.cs arquivo:

C#

using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;

Dentro do Form1 construtor, após a chamada de InitializeComponent método, adicione


o seguinte código:

C#

var services = new ServiceCollection();


services.AddWindowsFormsBlazorWebView();
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
blazorWebView1.RootComponents.Add<Counter>("#app");

7 Observação

O InitializeComponent método é gerado por um gerador de origem no tempo de


build do aplicativo e adicionado ao objeto de compilação para a classe de
chamada.

O código C# final e completo de Form1.cs :

C#

using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;

namespace WinFormsBlazor
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

var services = new ServiceCollection();


services.AddWindowsFormsBlazorWebView();
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
blazorWebView1.RootComponents.Add<Counter>("#app");
}
}
}

Executar o aplicativo
Selecione o botão Iniciar na barra de ferramentas do Visual Studio:

O aplicativo em execução no Windows:


Próximas etapas
Neste tutorial, você aprendeu a:

" Criar um projeto de aplicativo Windows Forms Blazor


" Executar o aplicativo no Windows

Saiba mais sobre Blazor Hybrid aplicativos:

ASP.NET Core Blazor Hybrid


Criar um aplicativo Blazor da WPF
(Windows Presentation Foundation)
Artigo • 24/12/2022 • 4 minutos para o fim da leitura

Este tutorial mostra como criar e executar um aplicativo WPF Blazor . Você aprenderá
como:

" Criar um projeto de aplicativo WPF Blazor


" Adicionar um Razor componente ao projeto
" Executar o aplicativo no Windows

Pré-requisitos
Plataformas com suporte (documentação do WPF)
Visual Studio 2022 com a carga de trabalho de desenvolvimento da área de
trabalho do .NET

Carga de trabalho do Visual Studio


Se a carga de trabalho de desenvolvimento da área de trabalho do .NET não estiver
instalada, use o instalador do Visual Studio para instalar a carga de trabalho. Para obter
mais informações, consulte Modificar cargas de trabalho, componentes e pacotes de
idiomas do Visual Studio.

Criar um projeto do WPF Blazor


Inicie o Visual Studio. Na Janela Iniciar, selecione Criar um novo projeto:
Na caixa de diálogo Criar um novo projeto , filtre a lista suspensa Tipo de projeto para
Área de Trabalho. Selecione o modelo de projeto C# para Aplicativo WPF e selecione o
botão Avançar :

Na caixa de diálogo Configurar seu novo projeto :

Defina o Nome do projeto como WpfBlazor.


Escolha um local adequado para o projeto.
Selecione o botão Avançar.

Na caixa de diálogo Informações adicionais , selecione a versão da estrutura com a lista


suspensa Estrutura . Selecione o botão Criar :
Use o Gerenciador de Pacotes NuGet para instalar o
Microsoft.AspNetCore.Components.WebView.Wpf pacote NuGet:

Em Gerenciador de Soluções, clique com o botão direito do mouse no nome do


projeto, WpfBlazore selecione Editar Arquivo de Projeto para abrir o arquivo de projeto
( WpfBlazor.csproj ).

Na parte superior do arquivo de projeto, altere o SDK para Microsoft.NET.Sdk.Razor :

XML

<Project Sdk="Microsoft.NET.Sdk.Razor">

No arquivo de projeto existente <PropertyGroup> , adicione a seguinte marcação para


definir o namespace raiz do aplicativo, que está WpfBlazor neste tutorial:

XML

<RootNamespace>WpfBlazor</RootNamespace>

7 Observação

As diretrizes anteriores sobre como definir o namespace raiz do projeto são uma
solução alternativa temporária. Para obter mais informações, consulte [Blazor]
[Wpf] Problema relacionado ao namespace raiz (dotnet/maui #5861) .

Salve as alterações no arquivo de projeto ( WpfBlazor.csproj ).

Adicione um _Imports.razor arquivo à raiz do projeto com uma @using diretiva para
Microsoft.AspNetCore.Components.Web.

_Imports.razor :

razor
@using Microsoft.AspNetCore.Components.Web

Salve o arquivo _Imports.razor .

Adicione uma wwwroot pasta ao projeto.

Adicione um index.html arquivo à wwwroot pasta com a marcação a seguir.

wwwroot/index.html :

HTML

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WpfBlazor</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="WpfBlazor.styles.css" rel="stylesheet" />
</head>

<body>
<div id="app">Loading...</div>

<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webview.js"></script>
</body>

</html>

Dentro da wwwroot pasta , crie uma css pasta.

Adicione uma app.css folha de estilos à wwwroot/css pasta com o conteúdo a seguir.

wwwroot/css/app.css :

css

html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1:focus {
outline: none;
}

a, .btn-link {
color: #0071c1;
}

.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}

.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}

.invalid {
outline: 1px solid red;
}

.validation-message {
color: red;
}

#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}

#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

Adicione o componente a seguir Counter à raiz do projeto, que é o componente padrão


Counter encontrado em Blazor modelos de projeto.

Counter.razor :

razor
<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Salve o Counter componente ( Counter.razor ).

Se o MainWindow designer não estiver aberto, abra-o clicando duas vezes no


MainWindow.xaml arquivo em Gerenciador de Soluções. MainWindow No designer,

substitua o código XAML pelo seguinte:

XAML

<Window x:Class="WpfBlazor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-
compatibility/2006"
xmlns:blazor="clr-
namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.Asp
NetCore.Components.WebView.Wpf"
xmlns:local="clr-namespace:WpfBlazor"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<blazor:BlazorWebView HostPage="wwwroot\index.html" Services="
{DynamicResource services}">
<blazor:BlazorWebView.RootComponents>
<blazor:RootComponent Selector="#app" ComponentType="{x:Type
local:Counter}" />
</blazor:BlazorWebView.RootComponents>
</blazor:BlazorWebView>
</Grid>
</Window>

Em Gerenciador de Soluções, clique com o botão MainWindow.xaml direito do mouse e


selecione Exibir Código:
Adicione o namespace Microsoft.Extensions.DependencyInjection à parte superior do
MainWindow.xaml.cs arquivo:

C#

using Microsoft.Extensions.DependencyInjection;

Dentro do MainWindow construtor, após a chamada de InitializeComponent método,


adicione o seguinte código:

C#

var serviceCollection = new ServiceCollection();


serviceCollection.AddWpfBlazorWebView();
Resources.Add("services", serviceCollection.BuildServiceProvider());

7 Observação

O InitializeComponent método é gerado por um gerador de origem no momento


da compilação do aplicativo e adicionado ao objeto de compilação para a classe de
chamada.

O código C# final completo de MainWindow.xaml.cs :

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Extensions.DependencyInjection;

namespace WpfBlazor
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

var serviceCollection = new ServiceCollection();


serviceCollection.AddWpfBlazorWebView();
Resources.Add("services",
serviceCollection.BuildServiceProvider());
}
}
}

Executar o aplicativo
Selecione o botão Iniciar na barra de ferramentas do Visual Studio:

O aplicativo em execução no Windows:


Próximas etapas
Neste tutorial, você aprendeu a:

" Criar um projeto de aplicativo WPF Blazor


" Adicionar um Razor componente ao projeto
" Executar o aplicativo no Windows

Saiba mais sobre Blazor Hybrid aplicativos:

ASP.NET Core Blazor Hybrid


Blazor Hybrid ASP.NET Core roteamento
e navegação
Artigo • 21/12/2022 • 5 minutos para o fim da leitura

Este artigo explica como gerenciar o roteamento de solicitações e a navegação em


Blazor Hybrid aplicativos.

Comportamento de roteamento de solicitação de URI padrão:

Um link será interno se o nome do host e o esquema corresponderem entre o URI


de origem do aplicativo e o URI de solicitação. Quando os nomes e esquemas de
host não correspondem ou se o link define target="_blank" , o link é considerado
externo.
Se o link for interno, o link será aberto no BlazorWebView pelo aplicativo.
Se o link for externo, o link será aberto por um aplicativo determinado pelo
dispositivo com base no manipulador registrado do dispositivo para o esquema do
link.
Para links internos que parecem solicitar um arquivo porque o último segmento do
URI usa notação de ponto (por exemplo, /file.x , , /Maryia.Melnyk /image.gif ),
mas não apontam para nenhum conteúdo estático:
WPF e Windows Forms: o conteúdo da página do host é retornado.
.NET MAUI: uma resposta 404 é retornada.

Para alterar o comportamento de manipulação de link para links que não definem
target="_blank" , registre o UrlLoading evento e defina a

UrlLoadingEventArgs.UrlLoadingStrategy propriedade . A UrlLoadingStrategy


enumeração permite definir o comportamento de manipulação de link para qualquer
um dos seguintes valores:

OpenExternally: carregue a URL usando um aplicativo determinado pelo


dispositivo. Essa é a estratégia padrão para URIs com um host externo.
OpenInWebView: carregue a URL dentro do BlazorWebView . Essa é a estratégia
padrão para URLs com um host que corresponde à origem do aplicativo. Não use
essa estratégia para links externos, a menos que você possa garantir que o URI
de destino seja totalmente confiável.
CancelLoad: cancela a tentativa de carregamento de URL atual.

A UrlLoadingEventArgs.Url propriedade é usada para obter ou definir dinamicamente a


URL.
2 Aviso

Por padrão, os links externos são abertos em um aplicativo determinado pelo


dispositivo. Abrir links externos em um BlazorWebView pode introduzir
vulnerabilidades de segurança e não deve ser habilitado, a menos que você possa
garantir que os links externos sejam totalmente confiáveis.

Documentação da API:

.NET MAUI: UrlLoading


WPF: UrlLoading
Windows Forms:UrlLoading

Namespace
O Microsoft.AspNetCore.Components.WebView namespace é necessário para os
exemplos neste artigo:

C#

using Microsoft.AspNetCore.Components.WebView;

Navegação interna
Adicione o manipulador de eventos a seguir ao construtor do Page em que o
BlazorWebView é criado, que está MainPage.xaml.cs em um aplicativo criado a partir do

.NET MAUI modelo de projeto.

C#

blazorWebView.UrlLoading +=
(sender, urlLoadingEventArgs) =>
{
if (urlLoadingEventArgs.Url.Host != "0.0.0.0")
{
urlLoadingEventArgs.UrlLoadingStrategy =
UrlLoadingStrategy.OpenInWebView;
}
};

Navegação externa
Registre-se no ExternalNavigationStarting evento e defina a propriedade para alterar o
ExternalLinkNavigationEventArgs.ExternalLinkNavigationPolicy comportamento de
navegação.

A ExternalLinkNavigationPolicy enumeração define o comportamento de navegação:

OpenInExternalBrowser : navegue até links externos usando o navegador padrão do

dispositivo. Essa é a política de navegação padrão.


InsecureOpenInWebView : navegue até links externos no Blazor WebView. Essa
política de navegação pode introduzir preocupações de segurança e não deve ser
habilitada, a menos que você possa garantir que todos os links externos sejam
totalmente confiáveis.
CancelNavigation : cancela a tentativa de navegação atual.

A ExternalLinkNavigationEventArgs.Uri propriedade contém o URI de destino.

2 Aviso

Por padrão, os links externos são abertos no navegador padrão do dispositivo. Não
é recomendável abrir links externos no Blazor WebView ( InsecureOpenInWebView ), a
menos que o conteúdo seja totalmente confiável.

Adicione o manipulador de eventos ao construtor da página em que o BlazorWebView é


construído:

C#

blazorWebView.ExternalNavigationStarting +=
(sender, externalLinkNavigationEventArgs) =>
{
externalLinkNavigationEventArgs.ExternalLinkNavigationPolicy =
ExternalLinkNavigationPolicy.OpenInExternalBrowser;
};
Blazor Hybrid ASP.NET Core arquivos
estáticos
Artigo • 08/12/2022 • 6 minutos para o fim da leitura

Este artigo descreve como consumir arquivos de ativos estáticos em Blazor Hybrid
aplicativos.

Em um Blazor Hybrid aplicativo, arquivos estáticos são recursos de aplicativo, acessados


por Razor componentes usando as seguintes abordagens:

.NET MAUI: .NET MAUI file system helpers


WPF e Windows Forms:ResourceManager

Quando ativos estáticos são usados apenas nos componentes, os Razor ativos estáticos
podem ser consumidos da raiz da Web ( wwwroot pasta) de maneira semelhante aos
Blazor WebAssembly aplicativos e Blazor Server . Para obter mais informações, consulte
a seção Ativos estáticos limitados a Razor componentes .

.NET MAUI
Em .NET MAUI aplicativos, ativos brutos que usam a ação MauiAsset de build e .NET
MAUI file system helpers são usados para ativos estáticos.

Coloque ativos brutos na Resources/Raw pasta do aplicativo. O exemplo nesta seção usa
um arquivo de texto estático.

Resources/Raw/Data.txt :

text

This is text from a static text file resource.

O seguinte Razor componente:

Chama OpenAppPackageFileAsync para obter um Stream para o recurso.


Lê o Stream com um StreamReader.
Chama StreamReader.ReadToEndAsync para ler o arquivo.

Pages/StaticAssetExample.razor :

razor
@page "/static-asset-example"
@using System.IO
@using Microsoft.Extensions.Logging
@using Microsoft.Maui.Storage
@inject ILogger<StaticAssetExample> Logger

<h1>Static Asset Example</h1>

<p>@dataResourceText</p>

@code {
public string dataResourceText = "Loading resource ...";

protected override async Task OnInitializedAsync()


{
try
{
using var stream =
await FileSystem.OpenAppPackageFileAsync("Data.txt");
using var reader = new StreamReader(stream);

dataResourceText = await reader.ReadToEndAsync();


}
catch (FileNotFoundException ex)
{
dataResourceText = "Data file not found.";
Logger.LogError(ex, "'Resource/Raw/Data.txt' not found.");
}
}
}

Para saber mais, consulte os recursos a seguir:

Direcionar várias plataformas de .NET MAUI um único projeto (.NET MAUI


documentação)
Melhorar a consistência com o redimensionador (dotnet/maui #4367)

WPF
Coloque o ativo em uma pasta do aplicativo, normalmente na raiz do projeto, como
uma Resources pasta. O exemplo nesta seção usa um arquivo de texto estático.

Resources/Data.txt :

text

This is text from a static text file resource.


Se uma Properties pasta não existir no aplicativo, crie uma Properties pasta na raiz do
aplicativo.

Se a Properties pasta não contiver um arquivo de recursos ( Resources.resx ), crie o


arquivo em Gerenciador de Soluções com o comando de menu contextual
Adicionar>Novo Item.

Clique duas vezes no Resource.resx arquivo.

SelecioneArquivos de Cadeias de Caracteres> na lista suspensa.

Selecione Adicionar Recurso>Adicionar Arquivo Existente. Se solicitado pelo Visual


Studio a confirmar a edição do arquivo, selecione Sim. Navegue até a Resources pasta,
selecione o Data.txt arquivo e selecione Abrir.

No componente de exemplo a seguir, ResourceManager.GetString obtém o texto do


recurso de cadeia de caracteres para exibição.

2 Aviso

Nunca use ResourceManager métodos com dados não confiáveis.

StaticAssetExample.razor :

razor

@page "/static-asset-example"
@using System.Resources

<h1>Static Asset Example</h1>

<p>@dataResourceText</p>

@code {
public string dataResourceText = "Loading resource ...";

protected override void OnInitialized()


{
var resources =
new ResourceManager(typeof(WpfBlazor.Properties.Resources));

dataResourceText = resources.GetString("Data") ?? "'Data' not


found.";
}
}
Windows Forms
Coloque o ativo em uma pasta do aplicativo, normalmente na raiz do projeto, como
uma Resources pasta. O exemplo nesta seção usa um arquivo de texto estático.

Resources/Data.txt :

text

This is text from a static text file resource.

Examine os arquivos associados Form1 a em Gerenciador de Soluções. Se Form1 não


tiver um arquivo de recurso ( .resx ), adicione um Form1.resx arquivo com o comando
de menu contextual Adicionar>Novo Item .

Clique duas vezes no Form1.resx arquivo.

SelecioneArquivos de Cadeias de Caracteres> na lista suspensa.

Selecione Adicionar Recurso>Adicionar Arquivo Existente. Se solicitado pelo Visual


Studio a confirmar a edição do arquivo, selecione Sim. Navegue até a Resources pasta,
selecione o Data.txt arquivo e selecione Abrir.

No seguinte componente de exemplo:

O nome do assembly do aplicativo é WinFormsBlazor . O ResourceManagernome


base do é definido como o nome do assembly de Form1 ( WinFormsBlazor.Form1 ).
ResourceManager.GetString obtém o texto do recurso de cadeia de caracteres
para exibição.

2 Aviso

Nunca use ResourceManager métodos com dados não confiáveis.

StaticAssetExample.razor :

razor

@page "/static-asset-example"
@using System.Resources

<h1>Static Asset Example</h1>

<p>@dataResourceText</p>
@code {
public string dataResourceText = "Loading resource ...";

protected override async Task OnInitializedAsync()


{
var resources =
new ResourceManager("WinFormsBlazor.Form1",
this.GetType().Assembly);

dataResourceText = resources.GetString("Data") ?? "'Data' not


found.";
}
}

Ativos estáticos limitados a Razor componentes


Um BlazorWebView controle tem um arquivo de host configurado (HostPage),
normalmente wwwroot/index.html . O HostPage caminho é relativo ao projeto. Todos os
ativos da Web estáticos (scripts, arquivos CSS, imagens e outros arquivos) referenciados
de um BlazorWebView são relativos ao seu configurado HostPage.

Os ativos da Web estáticos de uma Razor RCL (biblioteca de classes) usam caminhos
especiais: _content/{PACKAGE ID}/{PATH AND FILE NAME} . O espaço reservado {PACKAGE
ID} é a ID do pacote da biblioteca. A ID do pacote terá como valor padrão o nome do
assembly do projeto, se <PackageId> não for especificado no arquivo de projeto. O
{PATH AND FILE NAME} espaço reservado é o caminho e o nome do arquivo em wwwroot .

Esses caminhos são logicamente subcaminhos da pasta do wwwroot aplicativo, embora


estejam realmente vindo de outros pacotes ou projetos. Os pacotes de estilo CSS
específicos do componente também são criados na raiz da wwwroot pasta.

A raiz da Web do HostPage determina qual subconjunto de ativos estáticos estão


disponíveis:

wwwroot/index.html (Recomendado): todos os ativos na pasta do wwwroot


aplicativo estão disponíveis (por exemplo: wwwroot/image.png está disponível em
/image.png ), incluindo subpastas (por exemplo: wwwroot/subfolder/image.png está
disponível em /subfolder/image.png ). Os ativos estáticos RCL na pasta RCL
wwwroot estão disponíveis (por exemplo: wwwroot/image.png está disponível no

caminho _content/{PACKAGE ID}/image.png ), incluindo subpastas (por exemplo:


wwwroot/subfolder/image.png está disponível no caminho _content/{PACKAGE

ID}/subfolder/image.png ).
wwwroot/{PATH}/index.html : todos os ativos na pasta do aplicativo estão

disponíveis usando caminhos relativos da raiz da Web do wwwroot/{PATH}


aplicativo. Os ativos estáticos RCL no wwwroot/{PATH} não estão disponíveis porque
estariam em um local teórico inexistente, como ../../_content/{PACKAGE
ID}/{PATH} , que não é um caminho relativo com suporte.

wwwroot/_content/{PACKAGE ID}/index.html : todos os ativos na pasta da

wwwroot/{PATH} RCL estão disponíveis usando caminhos relativos da raiz da Web


da RCL. Os ativos estáticos do aplicativo no wwwroot/{PATH} não estão disponíveis
porque estariam em um local teórico inexistente, como ../../{PATH} , que não é
um caminho relativo com suporte.

Para a maioria dos aplicativos, recomendamos colocar o HostPage na raiz da wwwroot


pasta do aplicativo, que fornece a maior flexibilidade para fornecer ativos estáticos do
aplicativo, RCLs e por meio de subpastas do aplicativo e RCLs.

Os exemplos a seguir demonstram a referência de ativos estáticos da raiz da Web do


aplicativo ( wwwroot pasta) com um HostPage raizdo na wwwroot pasta.

wwwroot/data.txt :

text

This is text from a static text file resource.

Em Gerenciador de Soluções, selecione o data.txt arquivo. Nas Propriedades do


arquivo, defina Copiar para Diretório de Saída como Copiar se for mais recente.

A imagem do Jeep a® seguir também é usada no exemplo desta seção. Você pode
clicar com o botão direito do mouse na imagem a seguir para salvá-la localmente para
uso em um aplicativo de teste local.

wwwroot/jeep-yj.png :
7 Observação

Para imagens no wwwroot , a propriedade Copiar para Diretório de Saída usa a


configuração padrão de Não copiar.

Em um Razor componente:

O conteúdo do arquivo de texto estático pode ser lido usando as seguintes


técnicas:
.NET MAUI: .NET MAUI file system helpers (OpenAppPackageFileAsync)
WPF e Windows Forms:StreamReader.ReadToEndAsync
A imagem pode ser o atributo de origem ( src ) de uma marca de imagem ( <img> ).

StaticAssetExample2.razor :

razor

@page "/static-asset-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<StaticAssetExample2> Logger

<h1>Static Asset Example 2</h1>

<p>@dataResourceText</p>

<p><img alt="1991 Jeep YJ" src="/jeep-yj.png" /></p>

<p>
<em>Jeep</em> and <em>Jeep YJ</em> are registered trademarks of
<a href="https://www.stellantis.com">FCA US LLC (Stellantis NV)</a>.
</p>

@code {
public string dataResourceText = "Loading resource ...";

protected override async Task OnInitializedAsync()


{
try
{
dataResourceText = await ReadData();
}
catch (FileNotFoundException ex)
{
dataResourceText = "Data file not found.";
Logger.LogError(ex, "'wwwroot/data.txt' not found.");
}
}
}
Em .NET MAUI aplicativos, adicione o seguinte ReadData método ao @code bloco do
componente anterior:

C#

private async Task<string> ReadData()


{
using var stream = await
FileSystem.OpenAppPackageFileAsync("wwwroot/data.txt");
using var reader = new StreamReader(stream);

return await reader.ReadToEndAsync();


}

Em aplicativos WPF e Windows Forms, adicione o seguinte ReadData método ao @code


bloco do componente anterior:

C#

private async Task<string> ReadData()


{
using var reader = new StreamReader("wwwroot/data.txt");

return await reader.ReadToEndAsync();


}

Marcas Comerciais
Jeep e Jeep YJ são marcas registradas da FCA US LLC (Stellantis NV) .

Recursos adicionais
ResourceManager
Criar arquivos de recurso para aplicativos .NET (documentação de conceitos
básicos do .NET)
Como usar recursos em aplicativos localizáveis (documentação do WPF)
Usar ferramentas de desenvolvedor do
navegador com ASP.NET Core Blazor
Hybrid
Artigo • 21/12/2022 • 2 minutos para o fim da leitura

Este artigo explica como usar ferramentas de desenvolvedor do navegador com


Blazor Hybrid aplicativos.

Ferramentas de desenvolvedor do navegador


com .NET MAUIBlazor
Verifique se o Blazor Hybrid projeto está configurado para dar suporte às ferramentas
de desenvolvedor do navegador. Você pode confirmar o suporte às ferramentas de
desenvolvedor pesquisando o aplicativo por AddBlazorWebViewDeveloperTools .

Se o projeto ainda não estiver configurado para ferramentas de desenvolvedor do


navegador, adicione suporte:

1. Localizando onde a chamada para AddMauiBlazorWebView é feita, provavelmente


dentro do arquivo do MauiProgram.cs aplicativo.

2. Após a chamada para AddMauiBlazorWebView, adicione o seguinte código:

C#

#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
#endif

7 Observação

As diretrizes sobre as ferramentas de desenvolvedor de navegadores populares


podem ser encontradas na documentação de cada mantenedor do navegador:

Chrome DevTools
Visão geral das Ferramentas para Desenvolvedores do Microsoft Edge
Inspetor Web do Safari

Para usar ferramentas de desenvolvedor do navegador com um aplicativo do Windows:


1. Execute o .NET MAUIBlazor aplicativo para Windows e navegue até uma página de
aplicativo que usa um BlazorWebView.

2. Use o atalho de teclado Ctrl + Shift + I para abrir as ferramentas de


desenvolvedor do navegador.

3. Ferramentas de desenvolvedor fornecer uma variedade de recursos para trabalhar


com aplicativos, incluindo quais ativos a página solicitou, quanto tempo os ativos
levaram para carregar e o conteúdo dos ativos carregados. O exemplo a seguir
mostra a guia Console para ver as mensagens do console, que inclui todas as
mensagens de exceção geradas pela estrutura ou código do desenvolvedor:
Reutilizar Razor componentes no
ASP.NET Core Blazor Hybrid
Artigo • 28/11/2022 • 4 minutos para o fim da leitura

Este artigo explica como criar e organizar Razor componentes para a Web e Web Views
em Blazor Hybrid aplicativos.

Razoros componentes funcionam em modelos de hospedagem (Blazor


WebAssemblyBlazor Servere no Web View de Blazor Hybrid) e entre plataformas
(Android, iOS e Windows). Modelos e plataformas de hospedagem têm recursos
exclusivos que os componentes podem aproveitar, mas os componentes executados
entre modelos de hospedagem e plataformas devem aproveitar recursos exclusivos
separadamente, o que os exemplos a seguir demonstram:

Blazor WebAssembly dá suporte à interoperabilidade síncrona do JavaScript (JS),


que não é compatível com o canal de comunicação de interoperabilidade
estritamente assíncrono JS dentro Blazor Server e Web Views em aplicativos Blazor
Hybrid .
Componentes em um Blazor Server aplicativo podem acessar serviços que só estão
disponíveis no servidor, como um contexto de banco de dados da Entity
Framework.
Os componentes em uma área de BlazorWebView trabalho nativa podem acessar
diretamente os recursos de dispositivo móvel e de área de trabalho nativa, como
serviços de geolocalização. Blazor Server e Blazor WebAssembly os aplicativos
devem contar com interfaces de API Web de aplicativos em servidores externos
para fornecer recursos semelhantes.

Princípios de design
Para criar Razor componentes que possam funcionar perfeitamente entre modelos e
plataformas de hospedagem, siga os seguintes princípios de design:

Coloque o código de interface do usuário compartilhado em Razor RCLs


(bibliotecas de classes), que são contêineres projetados para manter partes
reutilizáveis da interface do usuário para uso em diferentes modelos e plataformas
de hospedagem.
Implementações de recursos exclusivos não devem existir em RCLs. Em vez disso, a
RCL deve definir abstrações (interfaces e classes base) que os modelos e
plataformas de hospedagem implementam.
Aceite apenas os recursos exclusivos hospedando o modelo ou a plataforma. Por
exemplo, Blazor WebAssembly dá suporte ao uso de IJSInProcessRuntime e
IJSInProcessObjectReference em um componente como otimização, mas usa-os
apenas com conversões condicionais e implementações de fallback que dependem
das abstrações e IJSObjectReference universais IJSRuntime que todos os modelos e
plataformas de hospedagem dão suporte. Para obter mais informações
sobreIJSInProcessRuntime, consulte Chamar funções JavaScript de métodos .NET
em ASP.NET Core Blazor. Para obter mais informações
sobreIJSInProcessObjectReference, consulte métodos .NET de chamadas de
funções JavaScript em ASP.NET Core Blazor.
Como regra geral, use CSS para estilo HTML em componentes. O caso mais
comum é a consistência na aparência de um aplicativo. Em locais em que os estilos
de interface do usuário devem diferir entre modelos ou plataformas de
hospedagem, use o CSS para definir as diferenças.
Se alguma parte da interface do usuário exigir conteúdo adicional ou diferente
para um modelo ou plataforma de hospedagem de destino, o conteúdo poderá
ser encapsulado dentro de um componente e renderizado dentro da RCL usando
DynamicComponent. A interface do usuário adicional também pode ser fornecida
aos componentes por meio de RenderFragment instâncias. Para obter mais
informações sobre RenderFragment, consulte fragmentos de renderização de
conteúdo filho e fragmentos de renderização para lógica de renderização
reutilizável.

Organização do código do projeto


Tanto quanto possível, coloque código e conteúdo estático em Razor RCLs (bibliotecas
de classes). Cada modelo de hospedagem ou plataforma faz referência à RCL e registra
implementações individuais na coleção de serviços do aplicativo que um Razor
componente pode exigir.

Cada assembly de destino deve conter apenas o código específico desse modelo ou
plataforma de hospedagem, juntamente com o código que ajuda a inicializar o
aplicativo.
Usar abstrações para recursos exclusivos
O exemplo a seguir demonstra como usar uma abstração para um serviço de localização
geográfica hospedando modelo e plataforma.

Em uma Razor RCL (biblioteca de classes) usada pelo aplicativo para obter dados
de localização geográfica para a localização do usuário em um mapa, o
MapComponent Razor componente injeta uma ILocationService abstração de

serviço.
App.Web para Blazor WebAssembly e Blazor Server projetos implementam
ILocationService como WebLocationService , que usa chamadas de API Web para

obter dados de localização geográfica.


App.Desktop para.NET MAUI, WPF e Windows Forms, implementem

ILocationService como DesktopLocationService . DesktopLocationService usa

recursos de dispositivo específicos da plataforma para obter dados de


geolocalização.
.NET MAUIBlazor código específico da
plataforma
Um padrão .NET MAUI comum é criar implementações separadas para diferentes
plataformas, como definir classes parciais com implementações específicas da
plataforma. Por exemplo, consulte o diagrama a seguir, no qual as classes parciais
CameraService são implementadas em cada uma delas CameraService.Windows.cs ,
CameraService.iOS.cs CameraService.Android.cs e CameraService.cs :

Quando você deseja empacotar recursos específicos da plataforma em uma biblioteca


de classes que pode ser consumida por outros aplicativos, recomendamos que você
siga uma abordagem semelhante à descrita no exemplo anterior e crie uma abstração
para o Razor componente:

Coloque o componente em uma Razor RCL (biblioteca de classes).


Em uma .NET MAUI biblioteca de classes, faça referência à RCL e crie as
implementações específicas da plataforma.
No aplicativo de consumo, faça referência à .NET MAUI biblioteca de classes.

O exemplo a seguir demonstra os conceitos de imagens em um aplicativo que organiza


fotografias:
Um .NET MAUIBlazor aplicativo usa InputPhoto de uma RCL que ele faz referência.
O .NET MAUI aplicativo também faz referência a uma .NET MAUI biblioteca de
classes.
InputPhoto na RCL injeta uma ICameraService interface, que é definida na RCL.
CameraService implementações de classe parciais estão ICameraService na .NET

MAUI biblioteca de classes ( CameraService.Windows.cs , CameraService.iOS.cs , ),


CameraService.Android.cs que faz referência à RCL.

Recursos adicionais
.NET MAUIBlazor aplicativo de exemplo de podcast
Código-fonte (microsoft/dotnet-podcasts repositório GitHub)
Aplicativo ao vivo
Autenticação e autorização do Blazor
Hybrid no ASP.NET Core
Artigo • 28/11/2022 • 25 minutos para o fim da leitura

Este artigo descreve o suporte do ASP.NET Core para a configuração e o gerenciamento


de segurança em aplicativos Identity e Blazor Hybrid do ASP.NET Core.

A autenticação em aplicativos Blazor Hybrid é tratada por bibliotecas de plataforma


nativas, pois elas oferecem garantias de segurança aprimoradas que a área restrita do
navegador não pode oferecer. A autenticação de aplicativos nativos usa um mecanismo
específico do sistema operacional ou um protocolo federado, como o OIDC (OpenID
Connect) . Siga as diretrizes para o provedor de identidade que você selecionou para o
aplicativo e integre ainda mais a identidade com Blazor usando as diretrizes neste
artigo.

A integração da autenticação deve atingir as seguintes metas para componentes e


serviços do Razor:

Usar as abstrações no pacote Microsoft.AspNetCore.Components.Authorization ,


como AuthorizeView.
Reagir a alterações no contexto da autenticação.
Acessar as credenciais provisionadas pelo aplicativo do provedor de identidade,
como tokens de acesso, para executar chamadas à API autorizadas.

Após a autenticação ser adicionada a um aplicativo .NET MAUI, WPF ou Windows Forms
e os usuários poderem fazer logon e logoff com êxito, integrar a autenticação com
Blazor para disponibilizar o usuário autenticado para componentes e serviços do Razor.
Execute as seguintes etapas:

Referência ao pacote Microsoft.AspNetCore.Components.Authorization .

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET,


consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de
consumo de pacotes (documentação do NuGet). Confirme as versões
corretas de pacote em NuGet.org .

Implemente um AuthenticationStateProvider personalizado, que é a abstração que


os componentes Razor usam para acessar informações sobre o usuário
autenticado e receber atualizações quando o estado de autenticação for alterado.

Registre o provedor de estado de autenticação personalizado no contêiner de


injeção de dependência.

As aplicativos .NET MAUI usam o Xamarin.Essentials: Web Authenticator: a classe


WebAuthenticator permite que o aplicativo inicie fluxos de autenticação baseados em

navegador que escutam um retorno de chamada para uma URL específica registrada
com o aplicativo.

Para obter diretrizes adicionais, consulte os seguintes recursos:

Autenticador da Web (.NET MAUI documentação


Sample.Server.WebAuthenticator aplicativo de exemplo

Criar um AuthenticationStateProvider
personalizado sem atualizações de alteração do
usuário
Se o aplicativo autenticar o usuário imediatamente após sua inicialização e o usuário
autenticado permanecer o mesmo durante todo o tempo de vida do aplicativo,
notificações de alteração do usuário não serão necessárias e o aplicativo fornecerá
apenas informações sobre o usuário autenticado. Nesse cenário, o usuário faz logon no
aplicativo quando ele é aberto, e o aplicativo exibe a tela de logon novamente depois
que o usuário faz logon. O ExternalAuthStateProvider a seguir é um exemplo de
implementação de um AuthenticationStateProvider personalizado para esse cenário de
autenticação.

7 Observação

O AuthenticationStateProvider personalizado a seguir não declara um namespace


para tornar o exemplo de código aplicável a qualquer aplicativo Blazor Hybrid. No
entanto, uma melhor prática é fornecer o namespace do aplicativo quando você
implementa o exemplo em um aplicativo de produção.

ExternalAuthStateProvider.cs :

C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class ExternalAuthStateProvider : AuthenticationStateProvider
{
private readonly Task<AuthenticationState> authenticationState;

public ExternalAuthStateProvider(AuthenticatedUser user) =>


authenticationState = Task.FromResult(new
AuthenticationState(user.Principal));

public override Task<AuthenticationState> GetAuthenticationStateAsync()


=>
authenticationState;
}

public class AuthenticatedUser


{
public ClaimsPrincipal Principal { get; set; } = new();
}

As seguintes etapas descrevem como:

Adicionar namespaces obrigatórios.


Adicionar os serviços de autorização e abstrações de Blazor à coleção de serviços.
Criar a coleção de serviços.
Resolva o serviço AuthenticatedUser para definir a entidade de declaração do
usuário autenticado. Consulte a documentação do provedor de identidade para
obter detalhes.
Retornar o host criado.

No método MauiProgram.CreateMauiApp de MainWindow.cs , adicione namespaces para


Microsoft.AspNetCore.Components.Authorization eSystem.Security.Claims:

C#

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

Remova a seguinte linha de código que retorna um Microsoft.Maui.Hosting.MauiApp


compilado:

diff

- return builder.Build();

Substitua a linha de código anterior pelo código a seguir. Adicione código do


OpenID/MSAL para autenticar o usuário. Consulte a documentação do provedor de
identidade para obter detalhes.
C#

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>


();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity
provider's
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new


ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

Criar um AuthenticationStateProvider
personalizado com atualizações de alteração
do usuário
Para atualizar o usuário enquanto o aplicativo Blazor está em execução, chame
NotifyAuthenticationStateChanged dentro da implementação de
AuthenticationStateProvider usando uma das seguintes abordagens:

Sinalizar uma atualização de autenticação de fora do BlazorWebView)


Manipular a autenticação dentro do BlazorWebView

Sinalizar uma atualização de autenticação de fora do


BlazorWebView (opção 1)

Um AuthenticationStateProvider personalizado pode usar um serviço global para


sinalizar uma atualização de autenticação. Recomendamos que o serviço ofereça um
evento que AuthenticationStateProvider possa assinar, em que o evento invoca
NotifyAuthenticationStateChanged.
7 Observação

O AuthenticationStateProvider personalizado a seguir não declara um namespace


para tornar o exemplo de código aplicável a qualquer aplicativo Blazor Hybrid. No
entanto, uma melhor prática é fornecer o namespace do aplicativo quando você
implementa o exemplo em um aplicativo de produção.

ExternalAuthStateProvider.cs :

C#

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider


{
private AuthenticationState currentUser;

public ExternalAuthStateProvider(ExternalAuthService service)


{
currentUser = new AuthenticationState(service.CurrentUser);

service.UserChanged += (newUser) =>


{
currentUser = new AuthenticationState(newUser);
NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
};
}

public override Task<AuthenticationState> GetAuthenticationStateAsync()


=>
Task.FromResult(currentUser);
}

public class ExternalAuthService


{
public event Action<ClaimsPrincipal>? UserChanged;
private ClaimsPrincipal? currentUser;

public ClaimsPrincipal CurrentUser


{
get { return currentUser ?? new(); }
set
{
currentUser = value;

if (UserChanged is not null)


{
UserChanged(currentUser);
}
}
}
}

No método MauiProgram.CreateMauiApp de MainWindow.cs , adicione um namespace para


Microsoft.AspNetCore.Components.Authorization:

C#

using Microsoft.AspNetCore.Components.Authorization;

Adicionar os serviços de autorização e abstrações de Blazor à coleção de serviços:

C#

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

Onde quer que o aplicativo autentique um usuário, resolva o serviço


ExternalAuthService :

C#

var authService = host.Services.GetRequiredService<ExternalAuthService>();

Execute o código OpenID/MSAL personalizado para autenticar o usuário. Consulte a


documentação do provedor de identidade para obter detalhes. O usuário autenticado
( authenticatedUser no exemplo a seguir) é um novo ClaimsPrincipal baseado em um
novo ClaimsIdentity.

Defina o usuário atual para o usuário autenticado:

C#

authService.CurrentUser = authenticatedUser;

Uma alternativa à abordagem anterior é definir a entidade de segurança do usuário


System.Threading.Thread.CurrentPrincipal em vez de defini-la por meio de um serviço, o
que evita o uso do contêiner de injeção de dependência:

C#
public class CurrentThreadUserAuthenticationStateProvider :
AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
=>
Task.FromResult(
new AuthenticationState(Thread.CurrentPrincipal as
ClaimsPrincipal ??
new ClaimsPrincipal(new ClaimsIdentity())));
}

Usando a abordagem alternativa, somente os serviços de autorização


(AddAuthorizationCore) e CurrentThreadUserAuthenticationStateProvider
( .TryAddScoped<AuthenticationStateProvider,
CurrentThreadUserAuthenticationStateProvider>() ) são adicionados à coleção de
serviços.

Manipular a autenticação dentro do BlazorWebView


(opção 2)
Um AuthenticationStateProvider personalizado pode incluir métodos adicionais para
disparar o logon e o logoff e atualizar o usuário.

7 Observação

O AuthenticationStateProvider personalizado a seguir não declara um namespace


para tornar o exemplo de código aplicável a qualquer aplicativo Blazor Hybrid. No
entanto, uma melhor prática é fornecer o namespace do aplicativo quando você
implementa o exemplo em um aplicativo de produção.

ExternalAuthStateProvider.cs :

C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider


{
private ClaimsPrincipal currentUser = new ClaimsPrincipal(new
ClaimsIdentity());

public override Task<AuthenticationState> GetAuthenticationStateAsync()


=>
Task.FromResult(new AuthenticationState(currentUser));

public Task LogInAsync()


{
var loginTask = LogInAsyncCore();
NotifyAuthenticationStateChanged(loginTask);

return loginTask;

async Task<AuthenticationState> LogInAsyncCore()


{
var user = await LoginWithExternalProviderAsync();
currentUser = user;

return new AuthenticationState(currentUser);


}
}

private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()


{
/*
Provide OpenID/MSAL code to authenticate the user. See your
identity
provider's documentation for details.

Return a new ClaimsPrincipal based on a new ClaimsIdentity.


*/
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

return Task.FromResult(authenticatedUser);
}

public void Logout()


{
currentUser = new ClaimsPrincipal(new ClaimsIdentity());
NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(currentUser)));
}
}

No exemplo anterior:

A chamada para LogInAsyncCore dispara o processo de logon.


A chamada para NotifyAuthenticationStateChanged notifica que uma atualização
está em andamento, o que permite que o aplicativo forneça uma interface do
usuário temporária durante o processo de logoon ou logoff.
Retornar loginTask retorna a tarefa de modo que o componente que disparou o
logon possa aguardar e reagir após a conclusão da tarefa.
O método LoginWithExternalProviderAsync é implementado pelo desenvolvedor
para fazer logon no usuário com o SDK do provedor de identidade. Para obter
mais informações, consulte a documentação do seu provedor de identidade. O
usuário autenticado ( authenticatedUser ) é um novo ClaimsPrincipal baseado em
um novo ClaimsIdentity.

No método MauiProgram.CreateMauiApp de MainWindow.cs , adicione os serviços de


autorização e as abstrações de Blazor à coleção de serviços:

C#

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();

O componente LoginComponent a seguir demonstra como fazer logon em um usuário.


Em um aplicativo típico, o componente LoginComponent só será mostrado em um
componente pai se o usuário não estiver conectado ao aplicativo.

Shared/LoginComponent.razor :

razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
public async Task Login()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.LoginAsync();
}
}

O componente LogoutComponent a seguir demonstra como fazer logoff de um usuário.


Em um aplicativo típico, o componente LogoutComponent só será mostrado em um
componente pai se o usuário estiver conectado ao aplicativo.

Shared/LogoutComponent.razor :

razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
public async Task Logout()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.Logout();
}
}

Acessando outras informações de autenticação


Blazor não define uma abstração para lidar com outras credenciais, como tokens de
acesso a serem usados para solicitações HTTP para APIs Web. É recomendável seguir as
diretrizes do provedor de identidade para gerenciar as credenciais do usuário com os
primitivos que o SDK do provedor de identidade fornece.

É comum que os SDKs do provedor de identidade usem um repositório de tokens para


credenciais de usuário armazenadas no dispositivo. Se o primitivo do repositório de
tokens do SDK for adicionado ao contêiner de serviço, consuma o primitivo do SDK
dentro do aplicativo.

A estrutura Blazor não está ciente das credenciais de autenticação de um usuário e não
interage com credenciais de forma alguma, portanto, o código do aplicativo fica livre
para seguir qualquer abordagem que você considerar mais conveniente. No entanto,
siga as diretrizes gerais de segurança na próxima seção, Outras considerações de
segurança de autenticação, ao implementar o código de autenticação em um aplicativo.

Outras considerações de segurança de


autenticação
O processo de autenticação é externo para Blazor, e recomendamos que os
desenvolvedores acessem as diretrizes do provedor de identidade para obter diretrizes
de segurança adicionais.

Ao implementar a autenticação:

Evite a autenticação no contexto do Web View. Por exemplo, evite usar uma
biblioteca OAuth do JavaScript para executar o fluxo de autenticação. Em um
aplicativo de página única, os tokens de autenticação não estão ocultos no
JavaScript e podem ser descobertos facilmente por usuários mal-intencionados e
usados para fins nefastos. Os aplicativos nativos não sofrem esse risco porque só
são capazes de obter tokens fora do contexto do navegador, o que significa que
scripts de terceiros não podem roubar os tokens e comprometer o aplicativo.
Evite implementar o fluxo de trabalho de autenticação por conta própria. Na
maioria dos casos, as bibliotecas da plataforma lidam com segurança com o fluxo
de trabalho de autenticação, usando o navegador do sistema em vez de usar um
Web View personalizado que pode ser sequestrado.
Evite usar o controle Web View da plataforma para executar a autenticação. Em vez
disso, use o navegador do sistema quando possível.
Evite passar os tokens para o contexto do documento (JavaScript). Em algumas
situações, uma biblioteca JavaScript dentro do documento é necessária para
executar uma chamada autorizada a um serviço externo. Em vez de disponibilizar o
token para o JavaScript por meio da interoperabilidade dom JS:
Forneça um token temporário gerado para a biblioteca e dentro do Web View.
Intercepte a solicitação de rede de saída no código.
Substitua o token temporário pelo token real e confirme se o destino da
solicitação é válido.

Recursos adicionais
Autenticação e autorização de Blazor no ASP.NET Core
Considerações de segurança do ASP.NET Core Blazor Hybrid
Considerações de segurança do ASP.NET
Core Blazor Hybrid
Artigo • 28/11/2022 • 5 minutos para o fim da leitura

Este artigo descreve considerações de segurança para Blazor Hybrid aplicativos.

Blazor Hybrid aplicativos que renderizam o conteúdo da Web executam código .NET
dentro de uma plataforma Web View. O código .NET interage com o conteúdo da Web
por meio de um canal de interoperabilidade entre o código .NET e o Web View.

O conteúdo da Web renderizado no Web View pode vir de ativos fornecidos pelo
aplicativo de qualquer um dos seguintes locais:

A wwwroot pasta no aplicativo.


Uma origem externa ao aplicativo. Por exemplo, uma fonte de rede, como a
Internet.

Existe um limite de confiança entre o código .NET e o código que é executado dentro
do Web View. O código .NET é fornecido pelo aplicativo e todos os pacotes de terceiros
confiáveis que você instalou. Depois que o aplicativo for criado, as fontes de conteúdo
do código Web View .NET não poderão ser alteradas.

Em contraste com as fontes de conteúdo do código .NET, as fontes de conteúdo do


código executado dentro do Web View aplicativo podem vir não apenas do aplicativo,
mas também de fontes externas. Por exemplo, ativos estáticos de uma CDN (Rede de
Distribuição de Conteúdo) externa podem ser usados ou renderizados por um aplicativo
Web View.

Considere o código dentro do Web View que não é confiável da mesma forma que o
código em execução dentro do navegador para um aplicativo Web não é confiável. As
mesmas ameaças e recomendações gerais de segurança se aplicam a recursos não
confiáveis em Blazor Hybrid aplicativos como para outros tipos de aplicativos.
Se possível, evite carregar conteúdo de uma origem de terceiros. Para atenuar riscos,
você pode ser capaz de fornecer conteúdo diretamente do aplicativo baixando os ativos
externos, verificando se eles são seguros para atender aos usuários e colocando-os na
pasta do wwwroot aplicativo para empacotamento com o restante do aplicativo. Quando
o conteúdo externo é baixado para inclusão no aplicativo, recomendamos a verificação
de vírus e malware antes de colocá-lo na wwwroot pasta do aplicativo.

Se seu aplicativo precisar fazer referência ao conteúdo de uma origem externa,


recomendamos que você use abordagens comuns de segurança da Web para fornecer
ao aplicativo uma oportunidade de bloquear o carregamento do conteúdo se o
conteúdo estiver comprometido:

Forneça conteúdo com segurança com TLS/HTTPS.


Institua uma CSP (Política de Segurança de Conteúdo).
Execute verificações de integridade de sub-recursos .

Mesmo que todos os recursos sejam empacotados no aplicativo e não sejam carregados
de nenhuma origem externa, permaneçam cautelosos quanto aos problemas no código
dos recursos executados dentro do Web View, pois os recursos podem ter
vulnerabilidades que podem permitir ataques XSS (script entre sites).

Em geral, a Blazor estrutura protege contra XSS lidando com HTML de maneiras
seguras. No entanto, alguns padrões de programação permitem que Razor os
componentes injetem HTML bruto na saída renderizada, como renderizar conteúdo de
uma fonte não confiável. Por exemplo, a renderização de conteúdo HTML diretamente
de um banco de dados deve ser evitada. Além disso, as bibliotecas JavaScript usadas
pelo aplicativo podem manipular HTML de maneiras não seguras para renderizar
inadvertidamente ou deliberadamente a saída não segura.

Por esses motivos, é melhor aplicar as mesmas proteções contra XSS que normalmente
são aplicadas a aplicativos Web. Impedir o carregamento de scripts de fontes
desconhecidas e não implementar recursos JavaScript potencialmente inseguros, como
eval e outros primitivos JavaScript não seguros. É recomendável estabelecer um CSP
para reduzir esses riscos de segurança.

Se o código dentro do Web View está comprometido, o código obtém acesso a todo o
conteúdo dentro do Web View host e pode interagir com o host por meio do canal de
interoperabilidade. Por esse motivo, qualquer conteúdo proveniente dos Web View
(eventos, JS interoperabilidade) deve ser tratado como não confiável e validado da
mesma forma que para outros contextos confidenciais, como em um aplicativo
comprometido Blazor Server que pode levar a ataques mal-intencionados no sistema
host.
Não armazene informações confidenciais, como credenciais, tokens de segurança ou
dados confidenciais do usuário, no contexto do Web View, pois disponibiliza as
informações para um invasor se elas Web View estiverem comprometidas. Há
alternativas mais seguras, como lidar com as informações confidenciais diretamente na
parte nativa do aplicativo.

Conteúdo externo renderizado em um iframe


Ao usar um iframe para exibir conteúdo externo em uma Blazor Hybrid página,
recomendamos que os usuários aproveitem os recursos de área restrita para garantir
que o conteúdo seja isolado da página pai que contém o aplicativo. No exemplo a
seguir, o sandbox atributo está presente para a <iframe> marca aplicar recursos de
área restrita à foo.html página:

HTML

<iframe sandbox src="https://contoso.com/foo.html" />

2 Aviso

Não sandbox há suporte para o atributo em versões iniciais do navegador. Para


obter mais informações, consulte Posso usar: sandbox .

Links para URLs externas


Por padrão, os links para URLs fora do aplicativo são abertos em um aplicativo externo
apropriado, não carregados dentro do Web View. Não recomendamos substituir o
comportamento padrão.

Manter a Web View corrente em aplicativos


implantados
Por padrão, o BlazorWebView controle usa o nativo Web Viewatualmente instalado e
específico da plataforma. Como o nativo Web View é atualizado periodicamente com
suporte para novas APIs e correções para problemas de segurança, talvez seja
necessário garantir que um aplicativo esteja usando uma Web View versão que atenda
aos requisitos do aplicativo.
Use uma das seguintes abordagens para manter a Web View corrente em aplicativos
implantados:

Em todas as plataformas: verifique a Web View versão e solicite que o usuário


execute as etapas necessárias para atualizá-la.
Somente no Windows: empacotar uma versão Web View fixa dentro do aplicativo,
usando-a no lugar do compartilhado Web Viewdo sistema.

Android
O Android Web View é distribuído e atualizado por meio da Google Play Store .
Verifique a Web View versão lendo a User-Agent cadeia de caracteres. Leia a Web
Viewpropriedade 's navigator.userAgent usando a interoperabilidade JavaScript e,
opcionalmente, armazene o valor usando um serviço singleton se a cadeia de caracteres
do agente de usuário for necessária fora de um Razor contexto de componente.

iOS/Mac Catalyst
O iOS e Mac Catalyst ambos usam WKWebView um controle baseado em Safari, que é
atualizado pelo sistema operacional. Semelhante ao caso do Android, determine a Web
View versão lendo a Web Viewcadeia de caracteres.User-Agent

Windows (.NET MAUI, WPF, Windows Forms)


No Windows, o Microsoft Edge WebView2 baseado em Chromium é necessário para
executar Blazor aplicativos Web.

Por padrão, a versão mais recente instalada de WebView2 , conhecida como , Evergreen
distributioné usada. Se você quiser enviar uma versão específica do WebView2 aplicativo,
use o Fixed Version distribution.

Para obter mais informações sobre como verificar a versão instalada WebView2 no
momento e os modos de distribuição, consulte a documentação deWebView2
distribuição.

Recursos adicionais
Autenticação e autorização de Blazor Hybrid no ASP.NET Core
Autenticação e autorização de Blazor no ASP.NET Core
Blazor estrutura do projeto ASP.NET
Core
Artigo • 28/11/2022 • 22 minutos para o fim da leitura

Este artigo descreve os arquivos e pastas que compõem um Blazor aplicativo gerado a
partir de um Blazor modelo de projeto.

Blazor Server
Blazor Server modelos de projeto: blazorserver , blazorserver-empty

Os Blazor Server modelos criam os arquivos iniciais e a estrutura de diretório para um


Blazor Server aplicativo:

Se o blazorserver modelo for usado, o aplicativo será preenchido com o seguinte:


Código de demonstração para um FetchData componente que carrega dados
de um serviço de previsão do tempo ( WeatherForecastService ) e interação do
usuário com um Counter componente.
Kit de ferramentas de front-end do Bootstrap .
Se o blazorserver-empty modelo for usado, o aplicativo será criado sem código de
demonstração e Bootstrap.

Estrutura do projeto:

Data folder: contém a WeatherForecast classe e a WeatherForecastService

implementação do que fornece dados meteorológicos de exemplo para o


componente do FetchData aplicativo.

Pages folder: contém os componentes/páginas roteáveis ( .razor ) que compõem o


Blazor aplicativo e a página raiz Razor de um Blazor Server aplicativo. A rota para
cada página é especificada usando a @page diretiva . O modelo inclui o seguinte:
_Host.cshtml : a página raiz do aplicativo implementada como uma Razor
Página:
Quando qualquer página do aplicativo é solicitada inicialmente, essa página
é renderizada e retornada na resposta.
A página Host especifica onde o componente raiz App ( App.razor ) é
renderizado.
Counter componente ( Counter.razor ): implementa a página Contador.
Error componente ( Error.razor ): renderizado quando ocorre uma exceção

sem tratamento no aplicativo.


FetchData componente ( FetchData.razor ): implementa a página Buscar dados.

Index componente ( Index.razor ): implementa a Home página.

Properties/launchSettings.json : mantém a configuração do ambiente de

desenvolvimento.

Shared folder: contém os seguintes componentes compartilhados e folhas de


estilos:
MainLayout componente ( MainLayout.razor ): o componente de layout do
aplicativo.
MainLayout.razor.css : folha de estilos para o layout principal do aplicativo.

NavMenu componente ( NavMenu.razor ): implementa a navegação na barra


lateral. Inclui o NavLink componente (NavLink), que renderiza links de
navegação para outros Razor componentes. O NavLink componente indica
automaticamente um estado selecionado quando seu componente é carregado,
o que ajuda o usuário a entender qual componente é exibido no momento.
NavMenu.razor.css : folha de estilos para o menu de navegação do aplicativo.
SurveyPrompt componente ( SurveyPrompt.razor ): Blazor componente de

pesquisa.

wwwroot : a pasta Raiz da Web do aplicativo que contém os ativos estáticos

públicos do aplicativo.

_Imports.razor : inclui diretivas comuns Razor a serem incluídas nos componentes

do aplicativo ( .razor ), como @using diretivas para namespaces.

App.razor : o componente raiz do aplicativo que configura o roteamento do lado


do cliente usando o Router componente . O Router componente intercepta a
navegação do navegador e renderiza a página que corresponde ao endereço
solicitado.

appsettings.json e arquivos de configurações de aplicativo ambiental: forneça

definições de configuração para o aplicativo.

Program.cs : o ponto de entrada do aplicativo que configura o host ASP.NET Core e

contém a lógica de inicialização do aplicativo, incluindo registros de serviço e


configuração do pipeline de processamento de solicitações:
Especifica os serviços de DI (injeção de dependência) do aplicativo. Os serviços
são adicionados chamando AddServerSideBlazore o WeatherForecastService é
adicionado ao contêiner de serviço para uso pelo componente de exemplo
FetchData .
Configura o pipeline de tratamento de solicitações do aplicativo:
MapBlazorHub é chamado para configurar um ponto de extremidade para a
conexão em tempo real com o navegador. A conexão é criada com SignalR,
que é uma estrutura para adicionar funcionalidade da Web em tempo real a
aplicativos.
MapFallbackToPage("/_Host") é chamado para configurar a página raiz do
aplicativo ( Pages/_Host.cshtml ) e habilitar a navegação.

Arquivos e pastas adicionais podem aparecer em um aplicativo produzido a partir de


um Blazor Server modelo de projeto quando opções adicionais são configuradas. Por
exemplo, gerar um aplicativo com ASP.NET Core Identity inclui ativos adicionais para
recursos de autenticação e autorização.

Blazor WebAssembly
Blazor WebAssembly modelos de projeto: blazorwasm , blazorwasm-empty

Os Blazor WebAssembly modelos criam os arquivos iniciais e a estrutura de diretório


para um Blazor WebAssembly aplicativo:

Se o blazorwasm modelo for usado, o aplicativo será preenchido com o seguinte:


Código de demonstração para um FetchData componente que carrega dados
de um ativo estático ( weather.json ) e interação do usuário com um Counter
componente.
Kit de ferramentas de front-end do Bootstrap .
Se o blazorwasm-empty modelo for usado, o aplicativo será criado sem código de
demonstração e Bootstrap.

Estrutura do projeto:

Pages folder: contém os componentes/páginas roteáveis ( .razor ) que compõem o


Blazor aplicativo. A rota para cada página é especificada usando a @page diretiva .
O modelo inclui os seguintes componentes:
Counter componente ( Counter.razor ): implementa a página Contador.
FetchData componente ( FetchData.razor ): implementa a página Buscar dados.

Index componente ( Index.razor ): implementa a Home página.

Properties/launchSettings.json : mantém a configuração do ambiente de

desenvolvimento.
Shared folder: contém os seguintes componentes compartilhados e folhas de

estilos:
MainLayout componente ( MainLayout.razor ): o componente de layout do

aplicativo.
MainLayout.razor.css : folha de estilos para o layout principal do aplicativo.

NavMenu componente ( NavMenu.razor ): implementa a navegação na barra

lateral. Inclui o NavLink componente (NavLink), que renderiza links de


navegação para outros Razor componentes. O NavLink componente indica
automaticamente um estado selecionado quando seu componente é carregado,
o que ajuda o usuário a entender qual componente é exibido no momento.
NavMenu.razor.css : folha de estilos para o menu de navegação do aplicativo.

SurveyPrompt componente ( SurveyPrompt.razor ): Blazor componente de


pesquisa.

wwwroot : a pasta Raiz da Web do aplicativo que contém os ativos estáticos


públicos do aplicativo, incluindo appsettings.json e arquivos de configurações de
aplicativo ambiental para definições de configuração. A index.html página da Web
é a página raiz do aplicativo implementada como uma página HTML:
Quando qualquer página do aplicativo é solicitada inicialmente, essa página é
renderizada e retornada na resposta.
A página especifica onde o componente raiz App é renderizado. O componente
é renderizado no local do elemento DOM div com um id de app ( <div
id="app">Loading...</div> ).

_Imports.razor : inclui diretivas comuns Razor a serem incluídas nos componentes

do aplicativo ( .razor ), como @using diretivas para namespaces.

App.razor : o componente raiz do aplicativo que configura o roteamento do lado

do cliente usando o Router componente . O Router componente intercepta a


navegação do navegador e renderiza a página que corresponde ao endereço
solicitado.

Program.cs : o ponto de entrada do aplicativo que configura o host WebAssembly:


O App componente é o componente raiz do aplicativo. O App componente é
especificado como o elemento DOM div com um id de app ( <div
id="app">Loading...</div> em wwwroot/index.html ) para a coleção de

componentes raiz ( builder.RootComponents.Add<App>("#app") ).


Os serviços são adicionados e configurados (por exemplo,
builder.Services.AddSingleton<IMyDependency, MyDependency>() ).
Arquivos e pastas adicionais podem aparecer em um aplicativo produzido a partir de
um Blazor WebAssembly modelo de projeto quando opções adicionais são
configuradas. Por exemplo, gerar um aplicativo com ASP.NET Core Identity inclui ativos
adicionais para recursos de autenticação e autorização.

Local do <head> conteúdo


Em Blazor Server aplicativos, <head> o conteúdo está localizado no Pages/_Host.cshtml
arquivo .

Em Blazor WebAssembly aplicativos, <head> o conteúdo está localizado no


wwwroot/index.html arquivo .

Dual Blazor Server/Blazor WebAssembly app


Para criar um aplicativo que pode ser executado como um Blazor Server aplicativo ou
um aplicativo, uma Blazor WebAssembly abordagem é colocar toda a lógica do
aplicativo e os componentes em uma Razor RCL (biblioteca de classes) e fazer referência
à RCL de projetos e Blazor WebAssembly separadosBlazor Server. Para serviços comuns
cujas implementações diferem com base no modelo de hospedagem, defina as
interfaces de serviço na RCL e implemente os serviços nos Blazor Server projetos e
Blazor WebAssembly .

Recursos adicionais
Ferramentas para ASP.NET Core Blazor
Modelos de hospedagem do ASP.NET Core Blazor
Referência rápida de APIs mínimas
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Conceitos básicos do ASP.NET Core
Blazor
Artigo • 26/09/2022 • 5 minutos para o fim da leitura

Artigos de conceitos básicos fornecem diretrizes sobre conceitos fundamentais de Blazor.


Alguns dos conceitos estão conectados a uma compreensão básica dos componentes
Razor, que são descritos com mais detalhes na próxima seção deste artigo e abordados
detalhadamente nos artigos de Componentes.

Componentes Razor
Aplicativos Blazor são baseados em componentes Razor, geralmente chamados apenas
de componentes. Um componente é um elemento da interface do usuário, como uma
página, uma caixa de diálogo ou um formulário de entrada de dados. Os componentes
são classes C# do .NET incorporados a assemblies .NET.

Razor refere-se a como os componentes geralmente são gravados no formulário de


uma página de marcação Razor para a lógica e a composição da interface do usuário do
lado do cliente. O Razor é uma sintaxe para a combinação de marcação HTML com o
código C# projetada para melhorar a produtividade do desenvolvedor. Arquivos Razor
usam a extensão de arquivo .razor .

Embora alguns desenvolvedores de Blazor e recursos online usem o termo


"componentes Blazor", a documentação evita esse termo e usa universalmente
"componentes Razor" ou "componentes".

A documentação do Blazor adota várias convenções para mostrar e discutir


componentes:

Código do projeto, caminhos de arquivo e nomes, nomes de modelo de projeto e


outros termos especializados estão em inglês dos Estados Unidos e geralmente
são limitados por código.
Os componentes geralmente são referenciados pelo nome da classe C# (Pascal
case) seguido pela palavra "componente". Por exemplo, um componente de
upload de arquivo típico é chamado de "componente FileUpload ".
Normalmente, o nome da classe C# de um componente é o mesmo que seu nome
de arquivo. Os caminhos de componente em um aplicativo geralmente são
indicados. Por exemplo, Pages/FileUpload.razor .
Os componentes roteáveis geralmente definem suas URLs relativas como o nome
da classe do componente em kebab case. Por exemplo, um componente
FileUpload inclui a configuração de roteamento para alcançar o componente

renderizado na URL relativa /file-upload . O roteamento e a navegação são


abordados em Roteamento e navegação do ASP.NET Core Blazor.
Quando várias versões de um componente são usadas, elas são numeradas
sequencialmente. Por exemplo, o componente FileUpload3 tem um nome de
arquivo e um local Pages/FileUpload3.razor e é atingido em /file-upload-3 .
Modificadores de acesso são usados em exemplos de artigo. Por exemplo, os
campos são private por padrão, mas estão presentes explicitamente no código
do componente. Por exemplo, private é declarado para declarar um campo
chamado maxAllowedFiles como private int maxAllowedFiles = 3; .
Geralmente, os exemplos aderem às convenções de codificação e às diretrizes de
engenharia do ASP.NET Core/C#. Para obter mais informações, consulte os
seguintes recursos:
Diretrizes de engenharia (repositório do GitHub dotnet/aspnetcore)
Convenções de codificação em C# (guia de C#)

Veja a seguir um componente de contador de exemplo e parte de um aplicativo criado a


partir de um modelo de projeto Blazor. A cobertura de componentes detalhada é
encontrada nos artigos de Componentes posteriormente na documentação. O exemplo
a seguir demonstra os conceitos de componente vistos nos artigos de Conceitos básicos
antes de chegar aos artigos de Componentes posteriormente na documentação.

Pages/Counter.razor :

razor

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}
O componente Counter anterior:

Define sua rota com a diretiva @page na primeira linha.


Define o título e o título de página.
Renderiza a contagem atual com @currentCount . currentCount é uma variável
inteira definida no código C# do bloco @code .
Exibe um botão para disparar o método IncrementCount , que também é
encontrado no bloco @code e aumenta o valor da variável currentCount .

Aplicativos de exemplo
Os aplicativos de exemplo de documentação estão disponíveis para inspeção e
download:

Repositório do GitHub dotnet/blazor-samples

O repositório contém dois tipos de exemplos:

Snippets de aplicativos de exemplo para Blazor Server e Blazor WebAssembly


fornecem os exemplos de código que aparecem nos artigos Blazor. Esses
aplicativos não são compilados e não são aplicativos executáveis. Eles são
fornecidas exclusivamente para fins de obtenção do código de exemplo do artigo.
Exemplos de aplicativos para acompanhar artigos Blazor compilados e executados
para os seguintes cenários:
Blazor Server com o EF Core
Blazor Server e Blazor WebAssembly com SignalR
Blazor WebAssembly registro em log habilitado para escopos

7 Observação

Nem todos os aplicativos de exemplo anteriores estão disponíveis para todas as


versões do ASP.NET Core.

Para obter mais informações, consulte o arquivo LEIAME.md do dotnet/blazor-


samples .

Solicitações de suporte
Somente problemas relacionados à documentação são apropriados para o repositório
dotnet/AspNetCore.Docs . Para suporte ao produto, não abra um problema de
documentação. Procure assistência por meio de um ou mais dos seguintes canais de
suporte:

Stack Overflow (com a marca: blazor)


Equipe Geral do Slack do ASP.NET Core
Blazor Gitter

Para relatar um possível bug na estrutura ou fazer comentários sobre o produto, abra
um problema para a unidade do produto ASP.NET Core em problemas do
dotnet/aspnetcore . Os relatórios de bug geralmente exigem o seguinte:

Explicação clara do problema: siga as instruções no modelo de problema do


GitHub fornecido pela unidade do produto ao abrir o problema.
Projeto de reprodução mínima: coloque um projeto no GitHub para que os
engenheiros de unidade de produto baixem e executem. Coloque um link cruzado
no comentário de abertura do problema.

Para um possível problema com um artigo o Blazor, abra um problema de


documentação. Para abrir um problema de documentação, use o botão de comentários
nesta página e o formulário na parte inferior do artigo e deixe os metadados ativos ao
criar o comentário de abertura. Os metadados fornecem dados de rastreamento e
pingam automaticamente o autor do artigo. Se o assunto foi discutido com a unidade
do produto, coloque um link cruzado para o problema de engenharia no comentário de
abertura da documentação.

Em caso de problemas ou comentários no Visual Studio ou no Visual Studio para Mac,


use os gestos Relatar um Problema ou Sugerir um Recurso de dentro do Visual Studio,
que abrem problemas internos para equipes do Visual Studio. Para obter mais
informações, confira Comentários do Visual Studio ou Como relatar um problema no
Visual Studio para Mac.

Para problemas com o Visual Studio Code, solicite suporte em fóruns de apoio à
comunidade. Para relatórios de bugs e comentários sobre produtos, abra um problema
no repositório do GitHub microsoft/vscode .

Os problemas do GitHub para documentação de Blazor são marcados automaticamente


para triagem no projeto Blazor.Docs (repositório do GitHub
dotnet/AspNetCore.Docs) . Aguarde um pouco por uma resposta, especialmente nos
fins de semana e feriados. Normalmente, os autores da documentação respondem
dentro de 24 horas durante a semana.

Links da comunidade para recursos Blazor


Para obter uma coleção de links para recursos Blazor mantidos pela comunidade, visite
Awesome Blazor .

7 Observação

A Microsoft não possui, mantém nem dá suporte ao Awesome Blazor e à maioria


dos produtos e serviços da comunidade descritos e vinculados lá.
Blazor ASP.NET Core roteamento e navegação
Artigo • 06/01/2023 • 68 minutos para o fim da leitura

Este artigo explica como gerenciar o roteamento de solicitações e como usar o NavLink componente para criar
links de navegação em Blazor aplicativos.

) Importante

Exemplos de código ao longo deste artigo mostram métodos chamados em Navigation , que é injetado
NavigationManager em classes e componentes.

Modelos de rota
O Router componente permite o roteamento para Razor componentes em um Blazor aplicativo. O Router
componente é usado no App componente de Blazor aplicativos.

App.razor :

razor

<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<p>Sorry, there's nothing at this address.</p>
</NotFound>
</Router>

Quando um Razor componente ( .razor ) com uma @page diretiva é compilado, a classe de componente gerada é
fornecida especificando RouteAttribute o modelo de rota do componente.

Quando o aplicativo é iniciado, o assembly especificado como o do AppAssembly Roteador é verificado para coletar
informações de rota para os componentes do aplicativo que têm um RouteAttribute.

Em runtime, o RouteView componente:

Recebe o RouteData do junto com quaisquer parâmetros de Router rota.


Renderiza o componente especificado com seu layout, incluindo quaisquer layouts aninhados adicionais.

Opcionalmente, especifique um DefaultLayout parâmetro com uma classe de layout para componentes que não
especificam um layout com a @layout diretiva . Os modelos de projeto daBlazor estrutura especificam o
MainLayout componente ( Shared/MainLayout.razor ) como o layout padrão do aplicativo. Para obter mais

informações sobre layouts, consulte layouts de ASP.NET CoreBlazor.

Os componentes dão suporte a vários modelos de rota usando várias@page diretivas. O componente de exemplo
a seguir é carregado em solicitações para /blazor-route e /different-blazor-route .

Pages/BlazorRoute.razor :

razor

@page "/blazor-route"
@page "/different-blazor-route"
<h1>Blazor routing</h1>

) Importante

Para que as URLs resolvam corretamente, o aplicativo deve incluir uma <base> marca (local do <head>
conteúdo) com o caminho base do aplicativo especificado no href atributo . Para obter mais informações,
consulte Hospedar e implantar ASP.NET Core Blazor.

Como alternativa para especificar o modelo de rota como um literal de cadeia de caracteres com a @page diretiva ,
modelos de rota baseados em constante podem ser especificados com a @attribute diretiva .

No exemplo a seguir, a @page diretiva em um componente é substituída pela @attribute diretiva e pelo modelo
de rota baseado em constante em Constants.CounterRoute , que é definido em outro lugar no aplicativo como
" /counter ":

diff

- @page "/counter"
+ @attribute [Route(Constants.CounterRoute)]

Concentrar um elemento na navegação


Use o FocusOnNavigate componente para definir o foco da interface do usuário como um elemento com base em
um seletor CSS depois de navegar de uma página para outra. Você pode ver o FocusOnNavigate componente em
uso pelo App componente de um aplicativo gerado a partir de um Blazor modelo de projeto.

Em App.razor :

razor

<FocusOnNavigate RouteData="@routeData" Selector="h1" />

Quando o Router componente navega para uma nova página, o FocusOnNavigate componente define o foco para
o cabeçalho de nível superior da página ( <h1> ). Essa é uma estratégia comum para garantir que uma navegação
de página seja anunciada ao usar um leitor de tela.

Fornecer conteúdo personalizado quando o conteúdo não for


encontrado
O Router componente permite que o aplicativo especifique conteúdo personalizado se o conteúdo não for
encontrado para a rota solicitada.

App No componente , defina o Router conteúdo personalizado no modelo do NotFound componente.

App.razor :

razor

<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<h1>Sorry</h1>
<p>Sorry, there's nothing at this address.</p>
</NotFound>
</Router>

Há suporte para itens arbitrários como conteúdo das <NotFound> marcas, como outros componentes interativos.
Para aplicar um layout padrão ao NotFound conteúdo, consulte layouts de ASP.NET CoreBlazor.

Rotear para componentes de vários assemblies


Use o AdditionalAssemblies parâmetro para especificar assemblies adicionais para o Router componente a ser
considerado ao pesquisar componentes roteáveis. Assemblies adicionais são verificados além do assembly
especificado para AppAssembly . No exemplo a seguir, Component1 é um componente roteável definido em uma
biblioteca de classes de componente referenciada. O exemplo a seguir AdditionalAssemblies resulta no suporte de
roteamento para Component1 .

App.razor :

razor

<Router
AppAssembly="@typeof(App).Assembly"
AdditionalAssemblies="new[] { typeof(Component1).Assembly }">
@* ... Router component elements ... *@
</Router>

Parâmetros de rota
O roteador usa parâmetros de rota para preencher os parâmetros de componente correspondentes com o mesmo
nome. Os nomes de parâmetro de rota não diferenciam maiúsculas de minúsculas. No exemplo a seguir, o text
parâmetro atribui o valor do segmento de rota à propriedade do Text componente. Quando uma solicitação é
feita para /route-parameter-1/amazing , o conteúdo da <h1> marca é renderizado como Blazor is amazing! .

Pages/RouteParameter1.razor :

razor

@page "/route-parameter-1/{text}"

<h1>Blazor is @Text!</h1>

@code {
[Parameter]
public string? Text { get; set; }
}

Há suporte para parâmetros opcionais. No exemplo a seguir, o parâmetro opcional text atribui o valor do
segmento de rota à propriedade do Text componente. Se o segmento não estiver presente, o valor de Text será
definido fantastic como .

Pages/RouteParameter2.razor :

razor
@page "/route-parameter-2/{text?}"

<h1>Blazor is @Text!</h1>

@code {
[Parameter]
public string? Text { get; set; }

protected override void OnInitialized()


{
Text = Text ?? "fantastic";
}
}

Use OnParametersSet em vez de para permitir a navegação do OnInitialized{Async} aplicativo para o mesmo
componente com um valor de parâmetro opcional diferente. Com base no exemplo anterior, use OnParametersSet
quando o usuário deve ser capaz de navegar de /route-parameter-2 ou para /route-parameter-2/amazing /route-
parameter-2/amazing /route-parameter-2 :

C#

protected override void OnParametersSet()


{
Text = Text ?? "fantastic";
}

Restrições de rota
Uma restrição de rota impõe a correspondência de tipo em um segmento de rota a um componente.

No exemplo a seguir, a rota para o User componente só corresponderá se:

Um Id segmento de rota está presente na URL de solicitação.


O Id segmento é um tipo inteiro ( int ).

Pages/User.razor :

razor

@page "/user/{Id:int}"

<h1>User Id: @Id</h1>

@code {
[Parameter]
public int Id { get; set; }
}

As restrições de rota mostradas na tabela a seguir estão disponíveis. Para as restrições de rota que correspondem
à cultura invariável, consulte o aviso abaixo da tabela para obter mais informações.

Constraint Exemplo Correspondências de exemplo Invariável


culture
correspondência

bool {active:bool} true , FALSE Não

datetime {dob:datetime} 2016-12-31 , 2016-12-31 7:32pm Sim


Constraint Exemplo Correspondências de exemplo Invariável
culture
correspondência

decimal {price:decimal} 49.99 , -1,000.01 Sim

double {weight:double} 1.234 , -1,001.01e8 Sim

float {weight:float} 1.234 , -1,001.01e8 Sim

guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 , {CD2C1638-1638-72D5-1638- Não


DEADBEEF1638}

int {id:int} 123456789 , -123456789 Sim

long {ticks:long} 123456789 , -123456789 Sim

2 Aviso

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 também funcionam com parâmetros opcionais. No exemplo a seguir, Id é necessário, mas
Option é um parâmetro de rota booliano opcional.

Pages/User.razor :

razor

@page "/user/{Id:int}/{Option:bool?}"

<p>
Id: @Id
</p>

<p>
Option: @Option
</p>

@code {
[Parameter]
public int Id { get; set; }

[Parameter]
public bool Option { get; set; }
}

Roteamento com URLs que contêm pontos


Para aplicativos e Blazor Server hospedadosBlazor WebAssembly, o modelo de rota padrão do lado do servidor
pressupõe que, se o último segmento de uma URL de solicitação contiver um ponto ( . ) que um arquivo é
solicitado. Por exemplo, a URL /example/some.thing relativa é interpretada pelo roteador como uma solicitação
para um arquivo chamado some.thing . Sem configuração adicional, um aplicativo retorna uma resposta 404 – Não
Encontrado se some.thing foi destinado a rotear para um componente com uma @page diretiva e some.thing é
um valor de parâmetro de rota. Para usar uma rota com um ou mais parâmetros que contenham um ponto, o
aplicativo deve configurar a rota com um modelo personalizado.

Considere o componente a seguir Example que pode receber um parâmetro de rota do último segmento da URL.
Pages/Example.razor :

razor

@page "/example/{param?}"

<p>
Param: @Param
</p>

@code {
[Parameter]
public string? Param { get; set; }
}

Para permitir que o Server aplicativo de uma solução hospedada Blazor WebAssembly roteie a solicitação com um
ponto no param parâmetro de rota, adicione um modelo de rota de arquivo de fallback com o parâmetro opcional
em Program.cs :

C#

app.MapFallbackToFile("/example/{param?}", "index.html");

Para configurar um Blazor Server aplicativo para rotear a solicitação com um ponto no param parâmetro de rota,
adicione um modelo de rota de página de fallback com o parâmetro opcional em Program.cs :

C#

app.MapFallbackToPage("/example/{param?}", "/_Host");

Saiba mais em Roteamento no ASP.NET Core.

Parâmetros de rota catch-all


Parâmetros de rota catch-all, que capturam caminhos entre vários limites de pasta, têm suporte em componentes.

Os parâmetros de rota catch-all são:

Nomeado para corresponder ao nome do segmento de rota. A nomenclatura não diferencia maiúsculas de
minúsculas.
Um tipo string . A estrutura não fornece conversão automática.
No final da URL.

Pages/CatchAll.razor :

razor

@page "/catch-all/{*pageRoute}"

@code {
[Parameter]
public string? PageRoute { get; set; }
}

Para a URL /catch-all/this/is/a/test com um modelo de rota de /catch-all/{*pageRoute} , o valor de PageRoute


é definido como this/is/a/test .
Barras e segmentos do caminho capturado são decodificados. Para um modelo de rota de /catch-
all/{*pageRoute} , a URL /catch-all/this/is/a%2Ftest%2A produz this/is/a/test* .

Auxiliares de URI e estado de navegação


Use NavigationManager para gerenciar URIs e navegação no código C#. NavigationManager fornece o evento e os
métodos mostrados na tabela a seguir.

Membro DESCRIÇÃO

Uri Obtém o URI absoluto atual.

BaseUri Obtém o URI base (com uma barra à direita) que pode ser anexado a caminhos de URI
relativos para produzir um URI absoluto. Normalmente, BaseUri corresponde ao href
atributo no elemento do <base> documento (local do <head> conteúdo).

NavigateTo Navega até o URI especificado. Se forceLoad for true :

O roteamento do lado do cliente é ignorado.


O navegador é forçado a carregar a nova página do servidor, independentemente de
o URI normalmente ser manipulado pelo roteador do lado do cliente.

Se replace for true , o URI atual no histórico do navegador será substituído em vez de
enviar por push um novo URI para a pilha de histórico.

LocationChanged Um evento que é acionado quando o local de navegação é alterado. Para obter mais
informações, consulte a seção Alterações de localização .

ToAbsoluteUri Converte um URI relativo em um URI absoluto.

ToBaseRelativePath Dado um URI base (por exemplo, um URI retornado anteriormente por BaseUri), converte
um URI absoluto em um URI relativo ao prefixo de URI base.

RegisterLocationChangingHandler Registra um manipulador para processar eventos de navegação de entrada. Chamar


NavigateTo sempre invoca o manipulador.

GetUriWithQueryParameter Retorna um URI construído atualizando NavigationManager.Uri com um único parâmetro


adicionado, atualizado ou removido. Para obter mais informações, consulte a seção Cadeias
de caracteres de consulta.

Alterações de local
Para o LocationChanged evento, LocationChangedEventArgs fornece as seguintes informações sobre eventos de
navegação:

Location: a URL do novo local.


IsNavigationIntercepted: se true , Blazor interceptou a navegação do navegador. Se false ,
NavigationManager.NavigateTo fez com que a navegação ocorresse.

O seguinte componente:

Navega até o componente do Counter aplicativo ( Pages/Counter.razor ) quando o botão é selecionado


usando NavigateTo.
Manipula o evento de alteração de local assinando para NavigationManager.LocationChanged.

O HandleLocationChanged método é desacionado quando Dispose é chamado pela estrutura .


Desacomplar o método permite a coleta de lixo do componente.

A implementação do agente registra as seguintes informações quando o botão é selecionado:


BlazorSample.Pages.Navigate: Information: URL of new location: https://localhost:{PORT}/counter

Pages/Navigate.razor :

razor

@page "/navigate"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code example</h1>

<button class="btn btn-primary" @onclick="NavigateToCounterComponent">


Navigate to the Counter component
</button>

@code {
private void NavigateToCounterComponent()
{
Navigation.NavigateTo("counter");
}

protected override void OnInitialized()


{
Navigation.LocationChanged += HandleLocationChanged;
}

private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)


{
Logger.LogInformation("URL of new location: {Location}", e.Location);
}

public void Dispose()


{
Navigation.LocationChanged -= HandleLocationChanged;
}
}

Para obter mais informações sobre o descarte de componentes, consulte ASP.NET Core Razor ciclo de vida do
componente.

Estado do histórico de navegação


O NavigationManager usa a API de Histórico do navegador para manter o estado do histórico de navegação
associado a cada alteração de local feita pelo aplicativo. Manter o estado do histórico é particularmente útil em
cenários de redirecionamento externo, como ao autenticar usuários com provedores de identidade externos. Para
obter mais informações, consulte a seção Opções de navegação .

Opções de navegação
Passe NavigationOptions para NavigateTo para controlar os seguintes comportamentos:

ForceLoad: ignore o roteamento do lado do cliente e force o navegador a carregar a nova página do
servidor, independentemente de o URI ser manipulado ou não pelo roteador do lado do cliente. O valor
padrão é false .
ReplaceHistoryEntry: substitua a entrada atual na pilha de histórico. Se false , acrescente a nova entrada à
pilha de histórico. O valor padrão é false .
HistoryEntryState: obtém ou define o estado a ser acrescentado à entrada de histórico.

C#

Navigation.NavigateTo("/path", new NavigationOptions


{
HistoryEntryState = "Navigation state"
});

Para obter mais informações sobre como obter o estado associado à entrada do histórico de destino durante o
tratamento de alterações de localização, consulte a seção Manipular/impedir alterações de localização .

Cadeias de consulta
Use o [SupplyParameterFromQuery] atributo com o [Parameter] atributo para especificar que um parâmetro de
componente de um componente roteável pode vir da cadeia de caracteres de consulta.

7 Observação

Os parâmetros de componente só podem receber valores de parâmetro de consulta em componentes


roteáveis com uma @page diretiva .

Os parâmetros de componente fornecidos da cadeia de caracteres de consulta dão suporte aos seguintes tipos:

bool , DateTime , decimal , double , float , Guid , int , long , string .


Variantes anuláveis dos tipos anteriores.
Matrizes dos tipos anteriores, sejam elas anuláveis ou não anuláveis.

A formatação invariável de cultura correta é aplicada para o tipo especificado (CultureInfo.InvariantCulture).

Especifique a [SupplyParameterFromQuery] propriedade do Name atributo para usar um nome de parâmetro de


consulta diferente do nome do parâmetro do componente. No exemplo a seguir, o nome C# do parâmetro de
componente é {COMPONENT PARAMETER NAME} . Um nome de parâmetro de consulta diferente é especificado para o
{QUERY PARAMETER NAME} espaço reservado:

C#

[Parameter]
[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]
public string? {COMPONENT PARAMETER NAME} { get; set; }

No exemplo a seguir com uma URL de /search?


filter=scifi%20stars&page=3&star=LeVar%20Burton&star=Gary%20Oldman :

A Filter propriedade é resolvida para scifi stars .


A Page propriedade é resolvida para 3 .
A Stars matriz é preenchida a partir de parâmetros de consulta chamados star ( Name = "star" ) e é
resolvida para LeVar Burton e Gary Oldman .

Pages/Search.razor :

razor

@page "/search"
<h1>Search Example</h1>

<p>Filter: @Filter</p>

<p>Page: @Page</p>

@if (Stars is not null)


{
<p>Assignees:</p>

<ul>
@foreach (var name in Stars)
{
<li>@name</li>
}
</ul>
}

@code {
[Parameter]
[SupplyParameterFromQuery]
public string? Filter { get; set; }

[Parameter]
[SupplyParameterFromQuery]
public int? Page { get; set; }

[Parameter]
[SupplyParameterFromQuery(Name = "star")]
public string[]? Stars { get; set; }
}

Use NavigationManager.GetUriWithQueryParameter para adicionar, alterar ou remover um ou mais parâmetros de


consulta na URL atual:

razor

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameter("{NAME}", {VALUE})

Para o exemplo anterior:

O {NAME} espaço reservado especifica o nome do parâmetro de consulta. O {VALUE} espaço reservado
especifica o valor como um tipo com suporte. Os tipos com suporte são listados posteriormente nesta seção.
Uma cadeia de caracteres é retornada igual à URL atual com um único parâmetro:
Adicionado se o nome do parâmetro de consulta não existir na URL atual.
Atualizado para o valor fornecido se o parâmetro de consulta existir na URL atual.
Removido se o tipo do valor fornecido for anulável e o valor for null .
A formatação invariável de cultura correta é aplicada para o tipo especificado (CultureInfo.InvariantCulture).
O nome e o valor do parâmetro de consulta são codificados em URL.
Todos os valores com o nome do parâmetro de consulta correspondente serão substituídos se houver várias
instâncias do tipo.

Chame NavigationManager.GetUriWithQueryParameters para criar um URI construído Uri com vários parâmetros
adicionados, atualizados ou removidos. Para cada valor, a estrutura usa value?.GetType() para determinar o tipo
de runtime para cada parâmetro de consulta e seleciona a formatação invariável de cultura correta. A estrutura
lança um erro para tipos sem suporte.
razor

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters({PARAMETERS})

O {PARAMETERS} espaço reservado é um IReadOnlyDictionary<string, object> .

Passe uma cadeia de caracteres de URI para GetUriWithQueryParameters para gerar um novo URI de um URI
fornecido com vários parâmetros adicionados, atualizados ou removidos. Para cada valor, a estrutura usa
value?.GetType() para determinar o tipo de runtime para cada parâmetro de consulta e seleciona a formatação

invariável de cultura correta. A estrutura lança um erro para tipos sem suporte. Os tipos com suporte são listados
posteriormente nesta seção.

razor

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters("{URI}", {PARAMETERS})

O {URI} espaço reservado é o URI com ou sem uma cadeia de caracteres de consulta.
O {PARAMETERS} espaço reservado é um IReadOnlyDictionary<string, object> .

Os tipos com suporte são idênticos aos tipos com suporte para restrições de rota:

bool
DateTime

decimal

double
float

Guid
int

long

string

Os tipos compatíveis incluem:

Variantes anuláveis dos tipos anteriores.


Matrizes dos tipos anteriores, sejam elas anuláveis ou não anuláveis.

Substituir um valor de parâmetro de consulta quando o parâmetro existir


C#

Navigation.GetUriWithQueryParameter("full name", "Morena Baccarin")

URL atual URL gerada

scheme://host/?full%20name=David%20Krumholtz&age=42 scheme://host/?full%20name=Morena%20Baccarin&age=42

scheme://host/?fUlL%20nAmE=David%20Krumholtz&AgE=42 scheme://host/?full%20name=Morena%20Baccarin&AgE=42
URL atual URL gerada

scheme://host/? scheme://host/?
full%20name=Jewel%20Staite&age=42&full%20name=Summer%20Glau full%20name=Morena%20Baccarin&age=42&full%20name=Morena%20Baccarin

scheme://host/?full%20name=&age=42 scheme://host/?full%20name=Morena%20Baccarin&age=42

scheme://host/?full%20name= scheme://host/?full%20name=Morena%20Baccarin

Acrescentar um parâmetro de consulta e um valor quando o parâmetro


não existir
C#

Navigation.GetUriWithQueryParameter("name", "Morena Baccarin")

URL atual URL gerada

scheme://host/?age=42 scheme://host/?age=42&name=Morena%20Baccarin

scheme://host/ scheme://host/?name=Morena%20Baccarin

scheme://host/? scheme://host/?name=Morena%20Baccarin

Remover um parâmetro de consulta quando o valor do parâmetro for null


C#

Navigation.GetUriWithQueryParameter("full name", (string)null)

URL atual URL gerada

scheme://host/?full%20name=David%20Krumholtz&age=42 scheme://host/?age=42

scheme://host/?full%20name=Sally%20Smith&age=42&full%20name=Summer%20Glau scheme://host/?age=42

scheme://host/?full%20name=Sally%20Smith&age=42&FuLl%20NaMe=Summer%20Glau scheme://host/?age=42

scheme://host/?full%20name=&age=42 scheme://host/?age=42

scheme://host/?full%20name= scheme://host/

Adicionar, atualizar e remover parâmetros de consulta


No exemplo a seguir:

name será removido, se presente.

age é adicionado com um valor de 25 ( int ), se não estiver presente. Se presente, age é atualizado para um
valor de 25 .
eye color é adicionado ou atualizado para um valor de green .

C#

Navigation.GetUriWithQueryParameters(
new Dictionary<string, object?>
{
["name"] = null,
["age"] = (int?)25,
["eye color"] = "green"
})

URL atual URL gerada

scheme://host/?name=David%20Krumholtz&age=42 scheme://host/?age=25&eye%20color=green

scheme://host/?NaMe=David%20Krumholtz&AgE=42 scheme://host/?age=25&eye%20color=green

scheme://host/?name=David%20Krumholtz&age=42&keepme=true scheme://host/?age=25&keepme=true&eye%20color=green

scheme://host/?age=42&eye%20color=87 scheme://host/?age=25&eye%20color=green

scheme://host/? scheme://host/?age=25&eye%20color=green

scheme://host/ scheme://host/?age=25&eye%20color=green

Suporte para valores enumeráveis


No exemplo a seguir:

full name é adicionado ou atualizado para Morena Baccarin , um único valor.

ping os parâmetros são adicionados ou substituídos por 35 , 87 16 e 240 .

C#

Navigation.GetUriWithQueryParameters(
new Dictionary<string, object?>
{
["full name"] = "Morena Baccarin",
["ping"] = new int?[] { 35, 16, null, 87, 240 }
})

URL atual URL gerada

scheme://host/? scheme://host/?
full%20name=David%20Krumholtz&ping=8&ping=300 full%20name=Morena%20Baccarin&ping=35&ping=16&ping=87&ping=240

scheme://host/? scheme://host/?
ping=8&full%20name=David%20Krumholtz&ping=300 ping=35&full%20name=Morena%20Baccarin&ping=16&ping=87&ping=240

scheme://host/? scheme://host/?
ping=8&ping=300&ping=50&ping=68&ping=42 ping=35&ping=16&ping=87&ping=240&full%20name=Morena%20Baccarin

Navegar com uma cadeia de caracteres de consulta adicionada ou


modificada
Para navegar com uma cadeia de caracteres de consulta adicionada ou modificada, passe uma URL gerada para
NavigateTo.

O exemplo a seguir chama:

GetUriWithQueryParameter para adicionar ou substituir o name parâmetro de consulta usando um valor de


Morena Baccarin .
Chama NavigateTo para disparar a navegação para a nova URL.
C#

Navigation.NavigateTo(
Navigation.GetUriWithQueryParameter("name", "Morena Baccarin"));

Interação do usuário com <Navigating> o conteúdo


O Router componente pode indicar ao usuário que uma transição de página está ocorrendo.

Na parte superior do App componente ( App.razor ), adicione uma @using diretiva para o
Microsoft.AspNetCore.Components.Routing namespace:

razor

@using Microsoft.AspNetCore.Components.Routing

Adicione uma <Navigating> marca ao componente com marcação a ser exibida durante eventos de transição de
página. Para obter mais informações, consulte Navigating (Documentação da API).

No conteúdo do elemento de roteador ( <Router>...</Router> ) do App componente ( App.razor ):

razor

<Navigating>
<p>Loading the requested page&hellip;</p>
</Navigating>

Para obter um exemplo que usa a Navigating propriedade , consulte Assemblies de carregamento lentos no
ASP.NET Core Blazor WebAssembly.

Manipular eventos de navegação assíncrona com


OnNavigateAsync
O Router componente dá suporte a um OnNavigateAsync recurso. O OnNavigateAsync manipulador é invocado
quando o usuário:

Visita uma rota pela primeira vez navegando até ela diretamente no navegador.
Navega para uma nova rota usando um link ou uma NavigationManager.NavigateTo invocação.

No componente App ( App.razor ):

razor

<Router AppAssembly="@typeof(App).Assembly"
OnNavigateAsync="@OnNavigateAsync">
...
</Router>

@code {
private async Task OnNavigateAsync(NavigationContext args)
{
...
}
}
Para obter um exemplo que usa OnNavigateAsync, consulte Assemblies de carregamento lentos no ASP.NET Core
Blazor WebAssembly.

Durante a pré-geração no servidor em um Blazor Server aplicativo ou aplicativo hospedado Blazor WebAssembly ,
OnNavigateAsync é executado duas vezes:

Uma vez, quando o componente de ponto de extremidade solicitado é renderizado estaticamente


inicialmente.
Uma segunda vez em que o navegador renderiza o componente de ponto de extremidade.

Para impedir que o código do desenvolvedor em OnNavigateAsync seja executado duas vezes, o App componente
pode armazenar o NavigationContext para uso no , em OnAfterRender{Async}que firstRender pode ser verificado.
Para obter mais informações, consulte Prerendering with JavaScript interop in the Blazor Lifecycle article.

Manipular cancelamentos no OnNavigateAsync


O NavigationContext objeto passado para o OnNavigateAsync retorno de chamada contém um CancellationToken
definido quando ocorre um novo evento de navegação. O OnNavigateAsync retorno de chamada deve ser gerado
quando esse token de cancelamento é definido para evitar continuar a executar o OnNavigateAsync retorno de
chamada em uma navegação desatualizada.

Se um usuário navegar até um ponto de extremidade, mas navegar imediatamente para um novo ponto de
extremidade, o aplicativo não deverá continuar executando o OnNavigateAsync retorno de chamada para o
primeiro ponto de extremidade.

No exemplo de componente a seguir App :

O token de cancelamento é passado na chamada para PostAsJsonAsync , que pode cancelar o POST se o
usuário sair do /about ponto de extremidade.
O token de cancelamento será definido durante uma operação de pré-busca do produto se o usuário sair do
/store ponto de extremidade.

App.razor :

razor

@inject HttpClient Http


@inject ProductCatalog Products

<Router AppAssembly="@typeof(App).Assembly"
OnNavigateAsync="@OnNavigateAsync">
...
</Router>

@code {
private async Task OnNavigateAsync(NavigationContext context)
{
if (context.Path == "/about")
{
var stats = new Stats { Page = "/about" };
await Http.PostAsJsonAsync("api/visited", stats,
context.CancellationToken);
}
else if (context.Path == "/store")
{
var productIds = [345, 789, 135, 689];

foreach (var productId in productIds)


{
context.CancellationToken.ThrowIfCancellationRequested();
Products.Prefetch(productId);
}
}
}
}

7 Observação

Não gerar se o token de cancelamento no NavigationContext for cancelado pode resultar em um


comportamento não intencional, como renderizar um componente de uma navegação anterior.

Manipular/impedir alterações de localização


RegisterLocationChangingHandler registra um manipulador para processar eventos de navegação de entrada. O
contexto do manipulador fornecido pelo LocationChangingContext inclui as seguintes propriedades:

TargetLocation: obtém o local de destino.


HistoryEntryState: obtém o estado associado à entrada do histórico de destino.
IsNavigationIntercepted: obtém se a navegação foi interceptada de um link.
CancellationToken: obtém um CancellationToken para determinar se a navegação foi cancelada, por exemplo,
para determinar se o usuário disparou uma navegação diferente.
PreventNavigation: chamado para impedir que a navegação continue.

Um componente pode registrar vários manipuladores de alteração de local em seus OnAfterRender métodos ou
OnAfterRenderAsync. A navegação invoca todos os manipuladores de alteração de local registrados em todo o
aplicativo (em vários componentes) e qualquer navegação interna executa todos eles em paralelo. Além
NavigateTo dos manipuladores, são invocados:

Ao selecionar links internos, que são links que apontam para URLs no caminho base do aplicativo.
Ao navegar usando os botões avançar e voltar em um navegador.

Os manipuladores são executados apenas para navegação interna dentro do aplicativo. Se o usuário selecionar um
link que navegue até um site diferente ou alterar a barra de endereços para um site diferente manualmente, os
manipuladores de alteração de localização não serão executados.

Implemente IDisposable e descarte manipuladores registrados para cancelá-los. Para saber mais, consulte Ciclo de
vida de renderização de Razor no ASP.NET Core.

No exemplo a seguir, um manipulador de alteração de local é registrado para eventos de navegação.

Pages/NavHandler.razor :

razor

@page "/nav-handler"
@inject NavigationManager Navigation
@implements IDisposable

<p>
<button @onclick="@(() => Navigation.NavigateTo("/"))">
Home (Allowed)
</button>
<button @onclick="@(() => Navigation.NavigateTo("/counter"))">
Counter (Prevented)
</button>
</p>
@code {
private IDisposable? registration;

protected override void OnAfterRender(bool firstRender)


{
if (firstRender)
{
registration =
Navigation.RegisterLocationChangingHandler(OnLocationChanging);
}
}

private ValueTask OnLocationChanging(LocationChangingContext context)


{
if (context.TargetLocation == "/counter")
{
context.PreventNavigation();
}

return ValueTask.CompletedTask;
}

public void Dispose() => registration?.Dispose();


}

Como a navegação interna pode ser cancelada de forma assíncrona, podem ocorrer várias chamadas sobrepostas
para manipuladores registrados. Por exemplo, várias chamadas de manipulador podem ocorrer quando o usuário
seleciona rapidamente o botão Voltar em uma página ou seleciona vários links antes que uma navegação seja
executada. Veja a seguir um resumo da lógica de navegação assíncrona:

Se algum manipulador de alteração de local for registrado, toda a navegação será inicialmente revertida e
reproduzida se a navegação não for cancelada.
Se forem feitas solicitações de navegação sobrepostas, a solicitação mais recente sempre cancelará
solicitações anteriores, o que significa o seguinte:
O aplicativo pode tratar várias seleções de botão voltar e avançar como uma única seleção.
Se o usuário selecionar vários links antes da navegação ser concluída, o último link selecionado
determinará a navegação.

Para obter mais informações sobre como passar NavigationOptions para NavigateTo entradas de controle e o
estado da pilha de histórico de navegação, consulte a seção Opções de navegação.

Para obter um código de exemplo adicional, consulte oNavigationManagerComponentBasicTestApp no


(dotnet/aspnetcore fonte de referência) .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam o branch padrão do
repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca
para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira
Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

O NavigationLock componente intercepta eventos de navegação desde que sejam renderizados, efetivamente
"bloqueando" qualquer navegação determinada até que seja tomada uma decisão de prosseguir ou cancelar. Use
NavigationLock quando a interceptação de navegação puder ter o escopo definido para o tempo de vida de um

componente.
Parâmetros de NavigationLock:

ConfirmExternalNavigation define uma caixa de diálogo do navegador para solicitar que o usuário confirme
ou cancele a navegação externa. O valor padrão é false . Exibir a caixa de diálogo de confirmação requer
interação inicial do usuário com a página antes de disparar a navegação externa com a URL na barra de
endereços do navegador. Para obter mais informações sobre o requisito de interação, consulte Janela:
beforeunload evento (documentação do MDN) .
OnBeforeInternalNavigation define um retorno de chamada para eventos de navegação interna.

No seguinte componente NavLock :

Uma tentativa de seguir o link para o site do Microsoft deve ser confirmada pelo usuário antes que a
navegação seja https://www.microsoft.com bem-sucedida.
PreventNavigation é chamado para impedir que a navegação ocorra se o usuário se recusar a confirmar a
navegação por meio de uma chamada de interoperabilidade javaScript (JS) que gera a JSconfirm caixa de
diálogo .

Pages/NavLock.razor :

razor

@page "/nav-lock"
@inject IJSRuntime JSRuntime
@inject NavigationManager Navigation

<NavigationLock ConfirmExternalNavigation="true"
OnBeforeInternalNavigation="OnBeforeInternalNavigation" />

<p>
<button @onclick="Navigate">Navigate</button>
</p>

<p>
<a href="https://www.microsoft.com">Microsoft homepage</a>
</p>

@code {
private void Navigate()
{
Navigation.NavigateTo("/");
}

private async Task OnBeforeInternalNavigation(LocationChangingContext context)


{
var isConfirmed = await JSRuntime.InvokeAsync<bool>("confirm",
"Are you sure you want to navigate to the Index page?");

if (!isConfirmed)
{
context.PreventNavigation();
}
}
}

Para obter um código de exemplo adicional, consulte o ConfigurableNavigationLock componente no BasicTestApp


(dotnet/aspnetcore fonte de referência) .

NavLink componentes e NavMenu


Use um NavLink componente no lugar de elementos de hiperlink HTML ( <a> ) ao criar links de navegação. Um
NavLink componente se comporta como um <a> elemento, exceto que alterna uma active classe CSS com base
em se corresponde href à URL atual. A active classe ajuda um usuário a entender qual página é a página ativa
entre os links de navegação exibidos. Opcionalmente, atribua um nome de classe CSS para NavLink.ActiveClass
aplicar uma classe CSS personalizada ao link renderizado quando a rota atual corresponder ao href .

7 Observação

O NavMenu componente ( NavMenu.razor ) é fornecido na Shared pasta de um aplicativo gerado a partir dos
Blazor modelos de projeto.

Há duas NavLinkMatch opções que você pode atribuir ao Match atributo do <NavLink> elemento :

NavLinkMatch.All: o NavLink está ativo quando corresponde a toda a URL atual.


NavLinkMatch.Prefix (padrão): o NavLink está ativo quando corresponde a qualquer prefixo da URL atual.

No exemplo anterior, o HomeNavLink href="" corresponde à URL inicial e recebe apenas a active classe CSS no
caminho base padrão do aplicativo (). / O segundo NavLink recebe a active classe quando o usuário visita
qualquer URL com um component prefixo (por exemplo, /component e /component/another-segment ).

Atributos de componente adicionais NavLink são passados para a marca de âncora renderizada. No exemplo a
seguir, o NavLink componente inclui o target atributo :

razor

<NavLink href="example-page" target="_blank">Example page</NavLink>

A seguinte marcação HTML é renderizada:

HTML

<a href="example-page" target="_blank">Example page</a>

2 Aviso

Devido à maneira como Blazor o renderiza o conteúdo filho, a renderização de NavLink componentes dentro
de um for loop exigirá uma variável de índice local se a variável de loop de incremento for usada no NavLink
conteúdo do componente (filho):

razor

@for (int c = 0; c < 10; c++)


{
var current = c;
<li ...>
<NavLink ... href="@c">
<span ...></span> @current
</NavLink>
</li>
}

O uso de uma variável de índice nesse cenário é um requisito para qualquer componente filho que usa uma
variável de loop em seu conteúdo filho, não apenas o NavLink componente.
Como alternativa, use um foreach loop com Enumerable.Range:

razor

@foreach (var c in Enumerable.Range(0,10))


{
<li ...>
<NavLink ... href="@c">
<span ...></span> @c
</NavLink>
</li>
}

ASP.NET Core integração de roteamento de ponto de


extremidade
Esta seção só se aplica a Blazor Server aplicativos.

Blazor Serveré integrado ao Roteamento de Ponto de Extremidade ASP.NET Core. Um aplicativo ASP.NET Core é
configurado para aceitar conexões de entrada para componentes interativos com MapBlazorHub no Program.cs :

C#

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

A configuração típica é rotear todas as solicitações para uma Razor página, que atua como o host para a parte do
lado do servidor do Blazor Server aplicativo. Por convenção, a página host geralmente é nomeada _Host.cshtml na
Pages pasta do aplicativo.

A rota especificada no arquivo de host é chamada de rota de fallback porque opera com baixa prioridade na
correspondência de rotas. A rota de fallback é usada quando outras rotas não correspondem. Isso permite que o
aplicativo use outros controladores e páginas sem interferir no roteamento de componentes no Blazor Server
aplicativo.
Configuração do ASP.NET Core Blazor
Artigo • 28/11/2022 • 16 minutos para o fim da leitura

Este artigo explica a configuração de Blazor aplicativos, incluindo configurações de


aplicativo, autenticação e log.

) Importante

Este tópico aplica-se ao Blazor WebAssembly. Para obter diretrizes gerais sobre
ASP.NET Core configuração do aplicativo, consulte Configuração em ASP.NET Core.

Blazor WebAssembly carrega a configuração dos seguintes arquivos de configurações


de aplicativo por padrão:

wwwroot/appsettings.json .
wwwroot/appsettings.{ENVIRONMENT}.json , em que o {ENVIRONMENT} espaço

reservado é o ambiente de runtime do aplicativo.

7 Observação

A configuração de log colocada em um arquivo de configurações de aplicativo em


wwwroot um Blazor WebAssembly aplicativo não é carregada por padrão. Para obter
informações, consulte a seção de configuração de log posteriormente neste artigo.

Outros provedores de configuração registrados pelo aplicativo também podem fornecer


configuração, mas nem todos os provedores ou recursos do provedor são apropriados
para Blazor WebAssembly aplicativos:

Provedor de configuração Key Vault do Azure: o provedor não tem suporte para
identidade gerenciada e ID do aplicativo (ID do cliente) com cenários de segredo
do cliente. A ID do aplicativo com um segredo do cliente não é recomendada para
nenhum aplicativo ASP.NET Core, especialmente Blazor WebAssembly aplicativos
porque o segredo do cliente não pode ser protegido do lado do cliente para
acessar o serviço Key Vault do Azure.
Azure App provedor de configuração: o provedor não é apropriado para Blazor
WebAssembly aplicativos porque Blazor WebAssembly os aplicativos não são
executados em um servidor no Azure.

2 Aviso
Os arquivos de configuração e configurações em um Blazor WebAssembly
aplicativo são visíveis para os usuários. Não armazene segredos do aplicativo,
credenciais ou outros dados confidenciais na configuração ou nos arquivos de
um Blazor WebAssembly aplicativo.

Para obter mais informações sobre provedores de configuração, consulte Configuração


em ASP.NET Core.

Configuração de configurações do aplicativo


A configuração nos arquivos de configurações do aplicativo é carregada por padrão. No
exemplo a seguir, um valor de configuração da interface do usuário é armazenado em
um arquivo de configurações de aplicativo e carregado pela Blazor estrutura
automaticamente. O valor é lido por um componente.

wwwroot/appsettings.json :

JSON

{
"h1FontSize": "50px"
}

Injete uma IConfiguration instância em um componente para acessar os dados de


configuração.

Pages/ConfigurationExample.razor :

razor

@page "/configuration-example"
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

<h1 style="font-size:@Configuration["h1FontSize"]">
Configuration example
</h1>

As restrições de segurança do cliente impedem o acesso direto aos arquivos, incluindo


arquivos de configurações para a configuração do aplicativo. Para ler arquivos de
configuração além appsettings.json / appsettings.{ENVIRONMENT}.json da pasta para a
wwwroot configuração, use um HttpClient.
2 Aviso

Os arquivos de configuração e configurações em um Blazor WebAssembly


aplicativo são visíveis para os usuários. Não armazene segredos do aplicativo,
credenciais ou outros dados confidenciais na configuração ou nos arquivos de
um Blazor WebAssembly aplicativo.

O exemplo a seguir lê um arquivo de configuração ( cars.json ) na configuração do


aplicativo.

wwwroot/cars.json :

JSON

{
"size": "tiny"
}

Adicione o namespace para Microsoft.Extensions.Configuration Program.cs :

C#

using Microsoft.Extensions.Configuration;

Em Program.cs , modifique o registro de serviço existente HttpClient para usar o cliente


para ler o arquivo:

C#

var http = new HttpClient()


{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
};

builder.Services.AddScoped(sp => http);

using var response = await http.GetAsync("cars.json");


using var stream = await response.Content.ReadAsStreamAsync();

builder.Configuration.AddJsonStream(stream);

Fonte de Configuração de Memória


O exemplo a seguir usa uma MemoryConfigurationSource Program.cs entrada para
fornecer configuração adicional.

Adicione o namespace para Microsoft.Extensions.Configuration.Memory Program.cs :

C#

using Microsoft.Extensions.Configuration.Memory;

Em Program.cs :

C#

var vehicleData = new Dictionary<string, string>()


{
{ "color", "blue" },
{ "type", "car" },
{ "wheels:count", "3" },
{ "wheels:brand", "Blazin" },
{ "wheels:brand:type", "rally" },
{ "wheels:year", "2008" },
};

var memoryConfig = new MemoryConfigurationSource { InitialData = vehicleData


};

builder.Configuration.Add(memoryConfig);

Injete uma IConfiguration instância em um componente para acessar os dados de


configuração.

Pages/MemoryConfig.razor :

razor

@page "/memory-config"
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

<h1>Memory configuration example</h1>

<h2>General specifications</h2>

<ul>
<li>Color: @Configuration["color"]</li>
<li>Type: @Configuration["type"]</li>
</ul>

<h2>Wheels</h2>
<ul>
<li>Count: @Configuration["wheels:count"]</li>
<li>Brand: @Configuration["wheels:brand"]</li>
<li>Type: @Configuration["wheels:brand:type"]</li>
<li>Year: @Configuration["wheels:year"]</li>
</ul>

Obtenha uma seção da configuração no código C# com IConfiguration.GetSection. O


exemplo a seguir obtém a wheels seção para a configuração no exemplo anterior:

razor

@code {
protected override void OnInitialized()
{
var wheelsSection = Configuration.GetSection("wheels");

...
}
}

Configuração de autenticação
Forneça a configuração de autenticação em um arquivo de configurações do aplicativo.

wwwroot/appsettings.json :

JSON

{
"Local": {
"Authority": "{AUTHORITY}",
"ClientId": "{CLIENT ID}"
}
}

Carregue a configuração para um Identity provedor com ConfigurationBinder.Bind


. Program.cs O exemplo a seguir carrega a configuração de um provedor OIDC.

Program.cs :

C#

builder.Services.AddOidcAuthentication(options =>
builder.Configuration.Bind("Local", options.ProviderOptions));
Configuração de log
Esta seção se aplica a Blazor WebAssembly aplicativos que configuram o log por meio de
um arquivo de configurações de aplicativo na wwwroot pasta.

Adicione o Microsoft.Extensions.Logging.Configuration pacote ao aplicativo.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

No arquivo de configurações do aplicativo, forneça a configuração de log. A


configuração de log é carregada em Program.cs .

wwwroot/appsettings.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

Em Program.cs :

C#

builder.Logging.AddConfiguration(
builder.Configuration.GetSection("Logging"));

Configuração do construtor de host


Ler a configuração do construtor de host de WebAssemblyHostBuilder.Configuration
dentro Program.cs .

Em Program.cs :
C#

var hostname = builder.Configuration["HostName"];

Configuração armazenada em cache


Os arquivos de configuração são armazenados em cache para uso offline. Com PWAs
(Aplicativos Web Progressivos), você só pode atualizar arquivos de configuração ao criar
uma nova implantação. A edição de arquivos de configuração entre implantações não
tem efeito porque:

Os usuários armazenaram em cache versões dos arquivos que continuam a usar.


Os arquivos e service-worker-assets.js PWA service-worker.js devem ser
recriados na compilação, que sinalizam para o aplicativo na próxima visita online
do usuário que o aplicativo foi reimplantado.

Para obter mais informações sobre como as atualizações em segundo plano são
tratadas por PWAs, consulte ASP.NET Core Blazor PWA (Aplicativo Web Progressivo).

Configuração de opções
A configuração de opções para Blazor WebAssembly aplicativos requer a adição de uma
referência de pacote para o Microsoft.Extensions.Options.ConfigurationExtensions
pacote NuGet.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Exemplo:

C#

builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));

Nem todos os recursos do ASP.NET Core Options têm suporte em Razor componentes.
Por exemplo, IOptionsSnapshot<TOptions> e IOptionsMonitor<TOptions> há suporte
para a configuração, mas não há suporte para recompilação de valores de opção para
essas interfaces fora do recarregamento do aplicativo solicitando o aplicativo em uma
nova guia do navegador ou selecionando o botão de recarregamento do navegador.
Apenas chamar StateHasChanged não atualiza os valores de opção de instantâneo ou
monitorado quando a configuração subjacente é alterada.
Blazor ASP.NET Core injeção de
dependência
Artigo • 24/12/2022 • 77 minutos para o fim da leitura

Por Rainer Stropek e Mike Rousos

Este artigo explica como Blazor os aplicativos podem injetar serviços em componentes.

A DI (injeção de dependência) é uma técnica para acessar os serviços configurados em


um local central:

Os serviços registrados na estrutura podem ser injetados diretamente em


componentes de Blazor aplicativos.
Blazor os aplicativos definem e registram serviços personalizados e os
disponibilizam em todo o aplicativo por meio da DI.

7 Observação

É recomendável ler Injeção de dependência em ASP.NET Core antes de ler este


tópico.

Serviços padrão
Os serviços mostrados na tabela a seguir são comumente usados em Blazor aplicativos.

Serviço Tempo de vida Descrição


Serviço Tempo de vida Descrição

HttpClient Com escopo Fornece métodos para enviar solicitações HTTP


e receber respostas HTTP de um recurso
identificado por um URI.

A instância de HttpClient em um Blazor


WebAssembly aplicativo é registrada pelo
aplicativo no Program.cs e usa o navegador
para lidar com o tráfego HTTP em segundo
plano.

Blazor Server Os aplicativos não incluem um


HttpClient configurado como um serviço por
padrão. Forneça um HttpClient para um Blazor
Server aplicativo.

Para obter mais informações, consulte Chamar


uma API Web de um aplicativo ASP.NET
CoreBlazor.

Um HttpClient é registrado como um serviço


com escopo, não singleton. Para obter mais
informações, consulte a seção Tempo de vida
do serviço .

IJSRuntime Blazor Representa uma instância de um runtime do


WebAssembly:Singleton JavaScript em que as chamadas JavaScript são
expedidas. Para obter mais informações,
Blazor Server:Escopo consulte Chamar funções JavaScript de métodos
.NET no ASP.NET Core Blazor.
A Blazor estrutura é
registrada IJSRuntime Ao tentar injetar o serviço em um serviço
no contêiner de serviço singleton em Blazor Server aplicativos, use uma
do aplicativo. das seguintes abordagens:

Altere o registro de serviço para com


escopo para corresponder IJSRuntimeao
registro de , o que é apropriado se o
serviço lida com o estado específico do
usuário.
Passe o IJSRuntime para a implementação
do serviço singleton como um argumento
de suas chamadas de método em vez de
injetá-lo no singleton.
Serviço Tempo de vida Descrição

NavigationManager Blazor Contém auxiliares para trabalhar com URIs e


WebAssembly:Singleton estado de navegação. Para obter mais
informações, consulte Auxiliares de estado de
Blazor Server:Escopo navegação e URI.

A Blazor estrutura é
registrada
NavigationManager no
contêiner de serviço do
aplicativo.

Serviços adicionais registrados pela Blazor estrutura são descritos na documentação em


que são usados para descrever Blazor recursos, como configuração e registro em log.

Um provedor de serviços personalizado não fornece automaticamente os serviços


padrão listados na tabela. Se você usar um provedor de serviços personalizado e exigir
qualquer um dos serviços mostrados na tabela, adicione os serviços necessários ao novo
provedor de serviços.

Adicionar serviços a um Blazor WebAssembly


aplicativo
Configure serviços para a coleção de serviços do aplicativo no Program.cs . No exemplo
a seguir, a ExampleDependency implementação é registrada para IExampleDependency :

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);


...
builder.Services.AddSingleton<IExampleDependency, ExampleDependency>();
...

await builder.Build().RunAsync();

Depois que o host for criado, os serviços estarão disponíveis no escopo da DI raiz antes
que os componentes sejam renderizados. Isso pode ser útil para executar a lógica de
inicialização antes de renderizar o conteúdo:

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);


...
builder.Services.AddSingleton<WeatherService>();
...
var host = builder.Build();

var weatherService = host.Services.GetRequiredService<WeatherService>();


await weatherService.InitializeWeatherAsync();

await host.RunAsync();

O host fornece uma instância de configuração central para o aplicativo. Com base no
exemplo anterior, a URL do serviço meteorológico é passada de uma fonte de
configuração padrão (por exemplo, appsettings.json ) para InitializeWeatherAsync :

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);


...
builder.Services.AddSingleton<WeatherService>();
...

var host = builder.Build();

var weatherService = host.Services.GetRequiredService<WeatherService>();


await weatherService.InitializeWeatherAsync(
host.Configuration["WeatherServiceUrl"]);

await host.RunAsync();

Adicionar serviços a um Blazor Server


aplicativo
Depois de criar um novo aplicativo, examine parte do Program.cs arquivo:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();

A builder variável representa um Microsoft.AspNetCore.Builder.WebApplicationBuilder


com um IServiceCollection, que é uma lista de objetos descritores de serviço . Os
serviços são adicionados fornecendo descritores de serviço à coleção de serviços. O
exemplo a seguir demonstra o conceito com a IDataAccess interface e sua
implementação DataAccess concreta:
C#

builder.Services.AddSingleton<IDataAccess, DataAccess>();

Registrar serviços comuns em uma solução


hospedada Blazor WebAssembly
Se um ou mais serviços comuns forem exigidos pelos Server projetos e Client de uma
solução hospedadaBlazor WebAssembly, você poderá colocar os registros de serviço
comuns em um método no Client projeto e chamar o método para registrar os serviços
em ambos os projetos.

Primeiro, fatore registros de serviço comuns em um método separado. Por exemplo, crie
um ConfigureCommonServices método no Client projeto:

C#

public static void ConfigureCommonServices(IServiceCollection services)


{
services.Add...;
}

Client No arquivo do Program.cs projeto, chame ConfigureCommonServices para registrar


os serviços comuns:

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);

...

ConfigureCommonServices(builder.Services);

Server No arquivo do Program.cs projeto, chame ConfigureCommonServices para


registrar os serviços comuns do Server projeto:

C#

var builder = WebApplication.CreateBuilder(args);

...

Client.Program.ConfigureCommonServices(builder.Services);
Para obter um exemplo dessa abordagem, consulte ASP.NET Core Blazor WebAssembly
cenários de segurança adicionais.

Tempo de vida do serviço


Os serviços podem ser configurados com os tempos de vida mostrados na tabela a
seguir.

Tempo Descrição
de vida

Scoped Blazor WebAssembly Atualmente, os aplicativos não têm um conceito de escopos de


DI. Scoped Os serviços registrados se comportam como Singleton serviços.

O Blazor Server modelo de hospedagem dá suporte ao Scoped tempo de vida entre


solicitações HTTP, mas não entre SignalR mensagens de conexão/circuito entre os
componentes carregados no cliente. A Razor parte Páginas ou MVC do aplicativo
trata os serviços com escopo normalmente e recria os serviços em cada solicitação
HTTP ao navegar entre páginas ou exibições ou de uma página ou exibição para um
componente. Os serviços com escopo não são reconstruídos ao navegar entre os
componentes no cliente, onde a comunicação com o servidor ocorre pela SignalR
conexão do circuito do usuário, não por meio de solicitações HTTP. Nos seguintes
cenários de componente no cliente, os serviços com escopo são reconstruídos
porque um novo circuito é criado para o usuário:

O usuário fecha a janela do navegador. O usuário abre uma nova janela e


navega de volta para o aplicativo.
O usuário fecha uma guia do aplicativo em uma janela do navegador. O usuário
abre uma nova guia e navega de volta para o aplicativo.
O usuário seleciona o botão recarregar/atualizar do navegador.

Para obter mais informações sobre como preservar o estado do usuário em serviços
com escopo em Blazor Server aplicativos, consulte ASP.NET Core Blazor modelos de
hospedagem.

Singleton A DI cria uma única instância do serviço. Todos os componentes que exigem um
Singleton serviço recebem a mesma instância do serviço.

Transient Sempre que um componente obtém uma instância de um Transient serviço do


contêiner de serviço, ele recebe uma nova instância do serviço.

O sistema de DI é baseado no sistema de DI em ASP.NET Core. Para obter mais


informações, consulte Injeção de dependência no ASP.NET Core.

Solicitar um serviço em um componente


Depois que os serviços forem adicionados à coleção de serviços, injete os serviços nos
componentes usando a @injectRazor diretiva , que tem dois parâmetros:

Tipo: o tipo do serviço a ser injetado.


Propriedade: o nome da propriedade que recebe o serviço de aplicativo injetado. A
propriedade não requer criação manual. O compilador cria a propriedade .

Para obter mais informações, consulte Injeção de dependência em exibições em


ASP.NET Core.

Use várias @inject instruções para injetar serviços diferentes.

O exemplo a seguir mostra como usar @inject. A implementação Services.IDataAccess


do serviço é injetada na propriedade DataRepository do componente . Observe como o
código está usando apenas a IDataAccess abstração:

razor

@page "/customer-list"
@inject IDataAccess DataRepository

@if (customers != null)


{
<ul>
@foreach (var customer in customers)
{
<li>@customer.FirstName @customer.LastName</li>
}
</ul>
}

@code {
private IReadOnlyList<Customer>? customers;

protected override async Task OnInitializedAsync()


{
customers = await DataRepository.GetAllCustomersAsync();
}

private class Customer


{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}

private interface IDataAccess


{
public Task<IReadOnlyList<Customer>> GetAllCustomersAsync();
}
}
Internamente, a propriedade gerada ( DataRepository ) usa o [Inject] atributo .
Normalmente, esse atributo não é usado diretamente. Se uma classe base for necessária
para componentes e propriedades injetadas também forem necessárias para a classe
base, adicione manualmente o [Inject] atributo :

C#

using Microsoft.AspNetCore.Components;

public class ComponentBase : IComponent


{
[Inject]
protected IDataAccess DataRepository { get; set; }

...
}

7 Observação

Como espera-se que os serviços injetados estejam disponíveis, não marque os


serviços injetados como anuláveis. Em vez disso, atribua um literal padrão com o
operador tolerante a nulo ( default! ). Por exemplo:

C#

[Inject]
private IExampleService ExampleService { get; set; } = default!;

Para saber mais, consulte os recursos a seguir:

NRTs (tipos de referência anuláveis) e análise estática de estado nulo do


compilador do .NET
Tipos de referência anuláveis (guia C#)
expressões de valor padrão (referência de C#)
! ! (tolerante a nulo) operador (referência do C#)

Em componentes derivados da classe base, a @inject diretiva não é necessária. O


InjectAttribute da classe base é suficiente:

razor

@page "/demo"
@inherits ComponentBase
<h1>Demo Component</h1>

Usar DI em serviços
Serviços complexos podem exigir serviços adicionais. No exemplo a seguir, DataAccess
requer o HttpClient serviço padrão. @inject (ou o [Inject] atributo) não está disponível
para uso em serviços. Em vez disso, a injeção de construtor deve ser usada. Os serviços
necessários são adicionados adicionando parâmetros ao construtor do serviço. Quando
a DI cria o serviço, ela reconhece os serviços necessários no construtor e os fornece
adequadamente. No exemplo a seguir, o construtor recebe um HttpClient via DI.
HttpClient é um serviço padrão.

C#

using System.Net.Http;

public class DataAccess : IDataAccess


{
public DataAccess(HttpClient http)
{
...
}
}

Pré-requisitos para injeção de construtor:

Um construtor deve existir cujos argumentos podem ser atendidos pela DI.
Parâmetros adicionais não cobertos pela DI serão permitidos se especificarem
valores padrão.
O construtor aplicável deve ser public .
Um construtor aplicável deve existir. No caso de uma ambiguidade, a DI gera uma
exceção.

Classes de componente base do utilitário para


gerenciar um escopo de DI
Em aplicativos ASP.NET Core, os serviços com escopo normalmente têm como escopo a
solicitação atual. Após a conclusão da solicitação, todos os serviços com escopo ou
transitórios são descartados pelo sistema de DI. Em Blazor Server aplicativos, o escopo
da solicitação dura a duração da conexão do cliente, o que pode resultar em serviços
transitórios e com escopo que duram muito mais do que o esperado. Em Blazor
WebAssembly aplicativos, os serviços registrados com um tempo de vida com escopo
são tratados como singletons, portanto, eles vivem mais do que os serviços com escopo
em aplicativos típicos ASP.NET Core.

7 Observação

Para detectar serviços transitórios descartáveis em um aplicativo, consulte as


seguintes seções:

Detectar descartáveis transitórios em Blazor WebAssembly aplicativosDetectar


descartáveis transitórios em Blazor Server aplicativos

Uma abordagem que limita o tempo de vida de um serviço em Blazor aplicativos é o


uso do OwningComponentBase tipo . OwningComponentBase é um tipo abstrato
derivado de ComponentBase que cria um escopo de DI correspondente ao tempo de
vida do componente. Usando esse escopo, é possível usar serviços de DI com um tempo
de vida com escopo e tê-los ativos enquanto o componente. Quando o componente é
destruído, os serviços do provedor de serviços com escopo do componente também
são descartados. Isso pode ser útil para serviços que:

Deve ser reutilizado dentro de um componente, pois o tempo de vida transitório é


inadequado.
Não deve ser compartilhado entre componentes, pois o tempo de vida singleton é
inadequado.

Duas versões do OwningComponentBase tipo estão disponíveis e descritas nas


próximas duas seções:

OwningComponentBase
OwningComponentBase<TService>

OwningComponentBase

OwningComponentBase é um filho abstrato e descartável do ComponentBase tipo com


uma propriedade protegida ScopedServices do tipo IServiceProvider. O provedor pode
ser usado para resolver serviços com escopo para o tempo de vida do componente.

Os serviços de DI injetados no componente usando @inject ou o [Inject] atributo não


são criados no escopo do componente. Para usar o escopo do componente, os serviços
devem ser resolvidos usando ScopedServices com ou GetRequiredServiceGetService.
Todos os serviços resolvidos usando o ScopedServices provedor têm suas dependências
fornecidas no escopo do componente.
O exemplo a seguir demonstra a diferença entre injetar um serviço com escopo
diretamente e resolver um serviço usando ScopedServices em um Blazor Server
aplicativo. A interface e a implementação a seguir para uma classe de viagem no tempo
incluem uma DT propriedade para manter um DateTime valor. A implementação chama
DateTime.Now para definir DT quando a TimeTravel classe é instanciada.

ITimeTravel.cs :

C#

public interface ITimeTravel


{
public DateTime DT { get; set; }
}

TimeTravel.cs :

C#

public class TimeTravel : ITimeTravel


{
public DateTime DT { get; set; } = DateTime.Now;
}

O serviço é registrado como com escopo em Program.cs um Blazor Server aplicativo.


Em um Blazor Server aplicativo, os serviços com escopo têm um tempo de vida igual à
duração da conexão do cliente, conhecido como circuito.

Em Program.cs :

C#

builder.Services.AddScoped<ITimeTravel, TimeTravel>();

No seguinte componente TimeTravel :

O serviço de viagem no tempo é injetado diretamente com @inject como


TimeTravel1 .
O serviço também é resolvido separadamente com ScopedServices e
GetRequiredService como TimeTravel2 .

Pages/TimeTravel.razor :

razor
@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase

<h1><code>OwningComponentBase</code> Example</h1>

<ul>
<li>TimeTravel1.DT: @TimeTravel1?.DT</li>
<li>TimeTravel2.DT: @TimeTravel2?.DT</li>
</ul>

@code {
private ITimeTravel? TimeTravel2 { get; set; }

protected override void OnInitialized()


{
TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
}
}

Se você estiver colocando este exemplo em um aplicativo de teste, adicione o


TimeTravel componente ao NavMenu componente .

Em Shared/NavMenu.razor :

razor

<div class="nav-item px-3">


<NavLink class="nav-link" href="time-travel">
<span class="oi oi-list-rich" aria-hidden="true"></span> Time travel
</NavLink>
</div>

Inicialmente navegando até o TimeTravel componente, o serviço de viagem no tempo é


instanciado duas vezes quando o componente é carregado e TimeTravel1 tem
TimeTravel2 o mesmo valor inicial:

TimeTravel1.DT: 8/31/2022 2:54:45 PM


TimeTravel2.DT: 8/31/2022 2:54:45 PM

Ao navegar do TimeTravel componente para outro componente e voltar para o


TimeTravel componente:

TimeTravel1 é fornecida a mesma instância de serviço que foi criada quando o


componente foi carregado pela primeira vez, portanto, o valor de DT permanece o
mesmo.
TimeTravel2 obtém uma nova ITimeTravel instância de serviço no TimeTravel2

com um novo valor DT.

TimeTravel1.DT: 8/31/2022 2:54:45 PM


TimeTravel2.DT: 8/31/2022 2:54:48 PM

TimeTravel1 está vinculado ao circuito do usuário, que permanece intacto e não é


descartado até que o circuito subjacente seja desconstruído. Por exemplo, o serviço será
descartado se o circuito estiver desconectado para o período de retenção do circuito
desconectado.

Apesar do registro de serviço com escopo no Program.cs e da longevidade do circuito


do usuário, TimeTravel2 recebe uma nova ITimeTravel instância de serviço sempre que
o componente é inicializado.

OwningComponentBase<TService>

OwningComponentBase<TService> deriva de OwningComponentBase e adiciona uma


Service propriedade que retorna uma instância de T do provedor de DI com escopo.
Esse tipo é uma maneira conveniente de acessar serviços com escopo sem usar uma
instância de IServiceProvider quando há um serviço primário que o aplicativo requer do
contêiner de DI usando o escopo do componente. A ScopedServices propriedade está
disponível, portanto, o aplicativo pode obter serviços de outros tipos, se necessário.

razor

@page "/users"
@attribute [Authorize]
@inherits OwningComponentBase<AppDbContext>

<h1>Users (@Service.Users.Count())</h1>

<ul>
@foreach (var user in Service.Users)
{
<li>@user.UserName</li>
}
</ul>

Uso de um DbContext do Entity Framework


Core (EF Core) da DI
Para obter mais informações, consulte ASP.NET Core Blazor Server com o Entity
Framework Core (EF Core).

Detectar descartáveis transitórios em Blazor


WebAssembly aplicativos
O exemplo a seguir mostra como detectar serviços transitórios descartáveis em um
aplicativo que deve usar OwningComponentBase. Para obter mais informações, consulte
a seção Classes de componente base do Utilitário para gerenciar um escopo de DI .

DetectIncorrectUsagesOfTransientDisposables.cs para Blazor WebAssembly aplicativos:

C#

using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection
{
using BlazorWebAssemblyTransientDisposable;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

public static class WebHostBuilderTransientDisposableExtensions


{
public static WebAssemblyHostBuilder
DetectIncorrectUsageOfTransients(
this WebAssemblyHostBuilder builder)
{
builder
.ConfigureContainer(
new
DetectIncorrectUsageOfTransientDisposablesServiceFactory());

return builder;
}

public static WebAssemblyHost EnableTransientDisposableDetection(


this WebAssemblyHost webAssemblyHost)
{
webAssemblyHost.Services
.GetRequiredService<ThrowOnTransientDisposable>
().ShouldThrow = true;

return webAssemblyHost;
}
}
}

namespace BlazorWebAssemblyTransientDisposable
{
public class DetectIncorrectUsageOfTransientDisposablesServiceFactory
: IServiceProviderFactory<IServiceCollection>
{
public IServiceCollection CreateBuilder(IServiceCollection services)
=>
services;

public IServiceProvider CreateServiceProvider(


IServiceCollection containerBuilder)
{
var collection = new ServiceCollection();

foreach (var descriptor in containerBuilder)


{
if (descriptor.Lifetime == ServiceLifetime.Transient &&
descriptor.ImplementationType != null &&
typeof(IDisposable).IsAssignableFrom(
descriptor.ImplementationType))
{
collection.Add(CreatePatchedDescriptor(descriptor));
}
else if (descriptor.Lifetime == ServiceLifetime.Transient &&
descriptor.ImplementationFactory != null)
{

collection.Add(CreatePatchedFactoryDescriptor(descriptor));
}
else
{
collection.Add(descriptor);
}
}

collection.AddScoped<ThrowOnTransientDisposable>();

return collection.BuildServiceProvider();
}

private ServiceDescriptor CreatePatchedFactoryDescriptor(


ServiceDescriptor original)
{
var newDescriptor = new ServiceDescriptor(
original.ServiceType,
(sp) =>
{
var originalFactory = original.ImplementationFactory;

if (originalFactory is null)
{
throw new InvalidOperationException(
"originalFactory is null.");
}

var originalResult = originalFactory(sp);

var throwOnTransientDisposable =
sp.GetRequiredService<ThrowOnTransientDisposable>();
if (throwOnTransientDisposable.ShouldThrow &&
originalResult is IDisposable d)
{
throw new InvalidOperationException("Trying to
resolve " +
$"transient disposable service
{d.GetType().Name} in " +
"the wrong scope. Use an
'OwningComponentBase<T>' " +
"component base class for the service 'T' you
are " +
"trying to resolve.");
}

return originalResult;
},
original.Lifetime);

return newDescriptor;
}

private ServiceDescriptor CreatePatchedDescriptor(ServiceDescriptor


original)
{
var newDescriptor = new ServiceDescriptor(
original.ServiceType,
(sp) => {
var throwOnTransientDisposable =
sp.GetRequiredService<ThrowOnTransientDisposable>();
if (throwOnTransientDisposable.ShouldThrow)
{
throw new InvalidOperationException("Trying to
resolve " +
"transient disposable service " +
$"{original.ImplementationType?.Name} in the wrong "
+
"scope. Use an 'OwningComponentBase<T>' component
base " +
"class for the service 'T' you are trying to
resolve.");
}

if (original.ImplementationType is null)
{
throw new InvalidOperationException(
"ImplementationType is null.");
}

return ActivatorUtilities.CreateInstance(sp,
original.ImplementationType);
},
ServiceLifetime.Transient);

return newDescriptor;
}
}

internal class ThrowOnTransientDisposable


{
public bool ShouldThrow { get; set; }
}
}

TransientDisposable.cs :

C#

public class TransientDisposable : IDisposable


{
public void Dispose() => throw new NotImplementedException();
}

O TransientDisposable no exemplo a seguir é detectado.

Program.cs :

C#

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using BlazorWebAssemblyTransientDisposable;

var builder = WebAssemblyHostBuilder.CreateDefault(args);


builder.DetectIncorrectUsageOfTransients();
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddTransient<TransientDisposable>();
builder.Services.AddScoped(sp =>
new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});

var host = builder.Build();


host.EnableTransientDisposableDetection();
await host.RunAsync();

O aplicativo pode registrar descartáveis transitórios sem gerar uma exceção. No


entanto, tentar resolver um descartável transitório resulta em um
InvalidOperationException, como mostra o exemplo a seguir.

Pages/TransientExample.razor :
razor

@page "/transient-example"
@inject TransientDisposable TransientDisposable

<h1>Transient Disposable Detection</h1>

Navegue até o TransientExample componente em /transient-example e um


InvalidOperationException é gerado quando a estrutura tenta construir uma instância
do TransientDisposable :

System.InvalidOperationException: tentando resolver o serviço descartável


transitório TransientDisposable no escopo errado. Use uma classe base de
componente 'OwningComponentBase<T>' para o serviço 'T' que você está
tentando resolver.

7 Observação

Os registros de serviço transitórios para IHttpClientFactory manipuladores são


recomendados. O TransientExample componente nesta seção indica os seguintes
descartáveis transitórios em Blazor WebAssembly aplicativos que usam
autenticação, o que é esperado:

BaseAddressAuthorizationMessageHandler
AuthorizationMessageHandler

Detectar descartáveis transitórios em Blazor


Server aplicativos
O exemplo a seguir mostra como detectar serviços transitórios descartáveis em um
aplicativo que deve usar OwningComponentBase. Para obter mais informações, consulte
a seção Classes de componente base do Utilitário para gerenciar um escopo de DI .

DetectIncorrectUsagesOfTransientDisposables.cs :

C#

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection
{
using BlazorServerTransientDisposable;

public static class WebHostBuilderTransientDisposableExtensions


{
public static WebApplicationBuilder
DetectIncorrectUsageOfTransients(
this WebApplicationBuilder builder)
{
builder.Host
.UseServiceProviderFactory(
new
DetectIncorrectUsageOfTransientDisposablesServiceFactory())
.ConfigureServices(
s =>
s.TryAddEnumerable(ServiceDescriptor.Scoped<CircuitHandler,
ThrowOnTransientDisposableHandler>()));

return builder;
}
}
}

namespace BlazorServerTransientDisposable
{
internal class ThrowOnTransientDisposableHandler : CircuitHandler
{
public ThrowOnTransientDisposableHandler(
ThrowOnTransientDisposable throwOnTransientDisposable)
{
throwOnTransientDisposable.ShouldThrow = true;
}
}

public class DetectIncorrectUsageOfTransientDisposablesServiceFactory


: IServiceProviderFactory<IServiceCollection>
{
public IServiceCollection CreateBuilder(IServiceCollection services)
=>
services;

public IServiceProvider CreateServiceProvider(


IServiceCollection containerBuilder)
{
var collection = new ServiceCollection();

foreach (var descriptor in containerBuilder)


{
if (descriptor.Lifetime == ServiceLifetime.Transient &&
descriptor.ImplementationType != null &&
typeof(IDisposable).IsAssignableFrom(
descriptor.ImplementationType))
{
collection.Add(CreatePatchedDescriptor(descriptor));
}
else if (descriptor.Lifetime == ServiceLifetime.Transient &&
descriptor.ImplementationFactory != null)
{

collection.Add(CreatePatchedFactoryDescriptor(descriptor));
}
else
{
collection.Add(descriptor);
}
}

collection.AddScoped<ThrowOnTransientDisposable>();

return collection.BuildServiceProvider();
}

private ServiceDescriptor CreatePatchedFactoryDescriptor(


ServiceDescriptor original)
{
var newDescriptor = new ServiceDescriptor(
original.ServiceType,
(sp) =>
{
var originalFactory = original.ImplementationFactory;

if (originalFactory is null)
{
throw new InvalidOperationException(
"originalFactory is null.");
}

var originalResult = originalFactory(sp);

var throwOnTransientDisposable =
sp.GetRequiredService<ThrowOnTransientDisposable>();
if (throwOnTransientDisposable.ShouldThrow &&
originalResult is IDisposable d)
{
throw new InvalidOperationException("Trying to
resolve " +
$"transient disposable service
{d.GetType().Name} in " +
"the wrong scope. Use an
'OwningComponentBase<T>' " +
"component base class for the service 'T' you
are " +
"trying to resolve.");
}

return originalResult;
},
original.Lifetime);

return newDescriptor;
}
private ServiceDescriptor CreatePatchedDescriptor(
ServiceDescriptor original)
{
var newDescriptor = new ServiceDescriptor(
original.ServiceType,
(sp) => {
var throwOnTransientDisposable =
sp.GetRequiredService<ThrowOnTransientDisposable>();
if (throwOnTransientDisposable.ShouldThrow)
{
throw new InvalidOperationException("Trying to
resolve " +
"transient disposable service " +
$"{original.ImplementationType?.Name} in the
wrong " +
"scope. Use an 'OwningComponentBase<T>'
component " +
"base class for the service 'T' you are trying
to " +
"resolve.");
}

if (original.ImplementationType is null)
{
throw new InvalidOperationException(
"ImplementationType is null.");
}

return ActivatorUtilities.CreateInstance(sp,
original.ImplementationType);
},
ServiceLifetime.Transient);

return newDescriptor;
}
}

internal class ThrowOnTransientDisposable


{
public bool ShouldThrow { get; set; }
}
}

TransitiveTransientDisposableDependency.cs :

C#

public class TransitiveTransientDisposableDependency


: ITransitiveTransientDisposableDependency, IDisposable
{
public void Dispose() { }
}
public interface ITransitiveTransientDisposableDependency
{
}

public class TransientDependency


{
private readonly ITransitiveTransientDisposableDependency
transitiveTransientDisposableDependency;

public TransientDependency(ITransitiveTransientDisposableDependency
transitiveTransientDisposableDependency)
{
this.transitiveTransientDisposableDependency =
transitiveTransientDisposableDependency;
}
}

O TransientDependency no exemplo a seguir é detectado.

Em Program.cs :

C#

builder.DetectIncorrectUsageOfTransients();
builder.Services.AddTransient<TransientDependency>();
builder.Services.AddTransient<ITransitiveTransientDisposableDependency,
TransitiveTransientDisposableDependency>();

O aplicativo pode registrar descartáveis transitórios sem gerar uma exceção. No


entanto, tentar resolver um descartável transitório resulta em um
InvalidOperationException, como mostra o exemplo a seguir.

Pages/TransientExample.razor :

razor

@page "/transient-example"
@inject TransientDependency TransientDependency

<h1>Transient Disposable Detection</h1>

Navegue até o TransientExample componente em /transient-example e um


InvalidOperationException é gerado quando a estrutura tenta construir uma instância
do TransientDependency :

System.InvalidOperationException: tentando resolver o serviço descartável


transitório TransientDependency no escopo errado. Use uma classe base de
componente 'OwningComponentBase<T>' para o serviço 'T' que você está
tentando resolver.

Acessar Blazor serviços de um escopo de DI


diferente
Esta seção só se aplica a Blazor Server aplicativos.*

Pode haver ocasiões em que um Razor componente invoca métodos assíncronos que
executam código em um escopo de DI diferente. Sem a abordagem correta, esses
escopos de DI não têm acesso aos Blazorserviços de , como IJSRuntime e
Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.

Por exemplo, HttpClient as instâncias criadas usando IHttpClientFactory têm seu próprio
escopo de serviço de DI. Como resultado, HttpMessageHandler as instâncias
configuradas no HttpClient não são capazes de injetar Blazor serviços diretamente.

Crie uma classe BlazorServiceAccessor que define um AsyncLocal, que armazena o


BlazorIServiceProvider para o contexto assíncrono atual. Uma BlazorServiceAcccessor
instância pode ser adquirida de dentro de um escopo de serviço de DI diferente para
acessar Blazor serviços.

BlazorServiceAccessor.cs :

C#

internal sealed class BlazorServiceAccessor


{
private static readonly AsyncLocal<BlazorServiceHolder>
s_currentServiceHolder = new();

public IServiceProvider? Services


{
get => s_currentServiceHolder.Value?.Services;
set
{
if (s_currentServiceHolder.Value is { } holder)
{
// Clear the current IServiceProvider trapped in the
AsyncLocal.
holder.Services = null;
}

if (value is not null)


{
// Use object indirection to hold the IServiceProvider in an
AsyncLocal
// so it can be cleared in all ExecutionContexts when it's
cleared.
s_currentServiceHolder.Value = new() { Services = value };
}
}
}

private sealed class BlazorServiceHolder


{
public IServiceProvider? Services { get; set; }
}
}

Para definir o valor de BlazorServiceAccessor.Services automaticamente quando um


async método de componente é invocado, crie um componente base personalizado

que reimplemente os três pontos de entrada assíncronos primários no Razor código do


componente:

IComponent.SetParametersAsync
IHandleEvent.HandleEventAsync
IHandleAfterRender.OnAfterRenderAsync

A classe a seguir demonstra a implementação do componente base.

CustomComponentBase.cs :

C#

using Microsoft.AspNetCore.Components;

public class CustomComponentBase : ComponentBase, IHandleEvent,


IHandleAfterRender
{
private bool hasCalledOnAfterRender;

[Inject]
private IServiceProvider Services { get; set; } = default!;

[Inject]
private BlazorServiceAccessor BlazorServiceAccessor { get; set; } =
default!;

public override Task SetParametersAsync(ParameterView parameters)


=> InvokeWithBlazorServiceContext(() =>
base.SetParametersAsync(parameters));

Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback,


object? arg)
=> InvokeWithBlazorServiceContext(() =>
{
var task = callback.InvokeAsync(arg);
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion
&&
task.Status != TaskStatus.Canceled;

StateHasChanged();

return shouldAwaitTask ?
CallStateHasChangedOnAsyncCompletion(task) :
Task.CompletedTask;
});

Task IHandleAfterRender.OnAfterRenderAsync()
=> InvokeWithBlazorServiceContext(() =>
{
var firstRender = !hasCalledOnAfterRender;
hasCalledOnAfterRender |= true;

OnAfterRender(firstRender);

return OnAfterRenderAsync(firstRender);
});

private async Task CallStateHasChangedOnAsyncCompletion(Task task)


{
try
{
await task;
}
catch
{
if (task.IsCanceled)
{
return;
}

throw;
}

StateHasChanged();
}

private async Task InvokeWithBlazorServiceContext(Func<Task> func)


{
try
{
BlazorServiceAccessor.Services = Services;
await func();
}
finally
{
BlazorServiceAccessor.Services = null;
}
}
}
Todos os componentes que se estendem CustomComponentBase automaticamente
definiram BlazorServiceAccessor.Services como o IServiceProvider no escopo de DI
atual Blazor .

Por fim, em Program.cs , adicione o BlazorServiceAccessor como um serviço com


escopo:

C#

var builder = WebApplication.CreateBuilder(args);


// ...
builder.Services.AddScoped<BlazorServiceAccessor>();
// ...

Recursos adicionais
Injeção de dependência no ASP.NET Core
IDisposable diretrizes para instâncias transitórias e compartilhadas
Injeção de dependência em exibições no ASP.NET Core
Blazor inicialização ASP.NET Core
Artigo • 10/01/2023 • 25 minutos para o fim da leitura

Este artigo explica como configurar Blazor a inicialização.

O Blazor processo de inicialização por meio do Blazor script ( blazor.


{webassembly|server}.js ) é automático e assíncrono. A Blazor <script> marca é
encontrada no wwwroot/index.html arquivo (Blazor WebAssembly) ou
Pages/_Host.cshtml arquivo (Blazor Server). Os scripts adicionados após a

Blazor <script> marca bloqueiam o Blazor mecanismo JavaScript até que eles terminem
de carregar.

Para iniciar Blazormanualmente :

Adicione um autostart="false" atributo e um valor à Blazor <script> marca.


Coloque um script que chame Blazor.start() após a Blazor <script> marca e
dentro da marca de fechamento </body> .

Inicializadores de JavaScript
Inicializadores de JavaScript (JS) executam a lógica antes e depois do carregamento de
um aplicativo Blazor. Inicializadores de JS são úteis nos seguintes cenários:

Personalizar como um aplicativo Blazor é carregado.


Inicializar bibliotecas antes de iniciar Blazor.
Definir configurações de Blazor.

Os inicializadores de JS são detectados como parte do processo de build e importados


automaticamente em aplicativos Blazor. O uso de inicializadores de JS geralmente
remove a necessidade de disparar manualmente funções de script do aplicativo ao usar
RCLs (bibliotecas de classes) de Razor.

Para definir um inicializador de JS, adicione um módulo JS ao projeto chamado


{NAME}.lib.module.js , em que o espaço reservado {NAME} é o nome do assembly, o
nome da biblioteca ou o identificador do pacote. Coloque o arquivo na raiz da Web do
projeto, que normalmente é a pasta wwwroot .

O módulo exporta uma das seguintes funções convencionais, ou ambas:

beforeStart(options, extensions) : chamado antes de Blazor começar. Por

exemplo, beforeStart é usado para personalizar o processo de carregamento, o


nível de registros em log e outras opções específicas para o modelo de
hospedagem.
Em Blazor WebAssembly, beforeStart recebe as opções de Blazor
WebAssembly ( options no exemplo desta seção) e de extensões ( extensions
no exemplo desta seção) adicionadas durante a publicação. Por exemplo, as
opções podem especificar o uso de um carregador de recursos de inicialização
personalizado.
Em Blazor Server, beforeStart recebe opções de início de circuito de SignalR
( options no exemplo desta seção).
Em BlazorWebViews, nenhuma opção é passada.
afterStarted : chamado após Blazor estar pronto para receber chamadas de JS. Por

exemplo, afterStarted é usado para inicializar bibliotecas fazendo chamadas de


interoperabilidade de JS e registrando elementos personalizados. A instância de
Blazor é passada para afterStarted como um argumento ( blazor no exemplo
desta seção).

O exemplo a seguir demonstra inicializadores de JS para beforeStart e afterStarted .


Para o nome do arquivo do seguinte exemplo:

Use o nome do assembly do aplicativo no nome do arquivo se os inicializadores de


JS forem consumidos como um ativo estático no projeto. Por exemplo, nomeie o
arquivo BlazorSample.lib.module.js para um projeto com o nome de assembly
BlazorSample . Coloque o arquivo na pasta wwwroot do aplicativo.
Use o nome da biblioteca ou o identificador do pacote do projeto se os
inicializadores JS forem consumidos de uma RCL. Por exemplo, nomeie o arquivo
RazorClassLibrary1.lib.module.js para uma RCL com um identificador de pacote
RazorClassLibrary1 . Coloque o arquivo na pasta wwwroot da biblioteca.

JavaScript

export function beforeStart(options, extensions) {


console.log("beforeStart");
}

export function afterStarted(blazor) {


console.log("afterStarted");
}

7 Observação

Aplicativos MVC e Razor Pages não carregam inicializadores JS automaticamente.


No entanto, o código do desenvolvedor pode incluir um script para buscar o
manifesto do aplicativo e disparar a carga dos inicializadores JS.

Para ver exemplos de inicializadores JS, consulte os seguintes recursos:

Layout de implantação para aplicativos Blazor WebAssembly ASP.NET Core


Aplicativo de Teste Básico no repositório do GitHub do ASP.NET Core
(BasicTestApp.lib.module.js)

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Inicializar Blazor quando o documento estiver


pronto
O exemplo a seguir começa Blazor quando o documento está pronto:

CSHTML

<body>
...

<script src="_framework/blazor.{webassembly|server}.js"
autostart="false"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
Blazor.start();
});
</script>
</body>

O espaço reservado {webassembly|server} na marcação anterior é webassembly para um


aplicativo Blazor WebAssembly ( blazor.webassembly.js ) ou server para um aplicativo
Blazor Server ( blazor.server.js ).
Encadear para o Promise que resulta de um
início manual
Para executar tarefas adicionais, como JS a inicialização de interoperabilidade, use
then para encadear o Promise que resulta de um início manual Blazor do aplicativo:

CSHTML

<body>
...

<script src="_framework/blazor.{webassembly|server}.js"
autostart="false"></script>
<script>
Blazor.start().then(function () {
...
});
</script>
</body>

O espaço reservado {webassembly|server} na marcação anterior é webassembly para um


aplicativo Blazor WebAssembly ( blazor.webassembly.js ) ou server para um aplicativo
Blazor Server ( blazor.server.js ).

Carregar recursos de inicialização


Esta seção só se aplica a Blazor WebAssembly aplicativos.

Quando um Blazor WebAssembly aplicativo é carregado no navegador, o aplicativo


baixa recursos de inicialização do servidor:

Código JavaScript para inicializar o aplicativo


Runtime e assemblies do .NET
Localizar dados específicos

Personalize como esses recursos de inicialização são carregados usando a


loadBootResource API. A loadBootResource função substitui o mecanismo interno de
carregamento de recursos de inicialização. Use loadBootResource para os seguintes
cenários:

Carregue recursos estáticos, como dados de fuso horário ou dotnet.wasm , de uma


CDN.
Carregue assemblies compactados usando uma solicitação HTTP e descompacte-
os no cliente para hosts que não dão suporte à busca de conteúdo compactado
do servidor.
Recursos de alias para um nome diferente redirecionando cada fetch solicitação
para um novo nome.

7 Observação

As fontes externas devem retornar os cabeçalhos cors (compartilhamento de


recursos entre origens) necessários para navegadores para permitir o
carregamento de recursos entre origens. As CDNs geralmente fornecem os
cabeçalhos necessários por padrão.

loadBootResource os parâmetros aparecem na tabela a seguir.

Parâmetro Descrição

type Tipo do recurso. Os tipos permitidos incluem: assembly , pdb , dotnetjs ,


dotnetwasm e timezonedata . Você só precisa especificar tipos para comportamentos
personalizados. Os tipos não especificados para loadBootResource são carregados
pela estrutura de acordo com seus comportamentos de carregamento padrão.

name O nome do recurso.

defaultUri O URI relativo ou absoluto do recurso.

integrity A cadeia de caracteres de integridade que representa o conteúdo esperado na


resposta.

A loadBootResource função pode retornar uma cadeia de caracteres de URI para


substituir o processo de carregamento. No exemplo a seguir, os seguintes arquivos de
bin/Release/net5.0/wwwroot/_framework são servidos de uma CDN em
https://cdn.example.com/blazorwebassembly/5.0.0/ :

dotnet.*.js
dotnet.wasm

Dados de fuso horário

Dentro da marca de fechamento </body> de wwwroot/index.html :

HTML

<script src="_framework/blazor.webassembly.js" autostart="false"></script>


<script>
Blazor.start({
loadBootResource: function (type, name, defaultUri, integrity) {
console.log(`Loading: '${type}', '${name}', '${defaultUri}',
'${integrity}'`);
switch (type) {
case 'dotnetjs':
case 'dotnetwasm':
case 'timezonedata':
return `https://cdn.example.com/blazorwebassembly/5.0.0/${name}`;
}
}
});
</script>

Para personalizar mais do que apenas as URLs para recursos de inicialização, a


loadBootResource função pode chamar fetch diretamente e retornar o resultado. O

exemplo a seguir adiciona um cabeçalho HTTP personalizado às solicitações de saída.


Para manter o comportamento de verificação de integridade padrão, passe pelo
integrity parâmetro .

Dentro da marca de fechamento </body> de wwwroot/index.html :

HTML

<script src="_framework/blazor.webassembly.js" autostart="false"></script>


<script>
Blazor.start({
loadBootResource: function (type, name, defaultUri, integrity) {
return fetch(defaultUri, {
cache: 'no-cache',
integrity: integrity,
headers: { 'Custom-Header': 'Custom Value' }
});
}
});
</script>

A loadBootResource função também pode retornar:

null / undefined , que resulta no comportamento de carregamento padrão.


Uma Response promessa . Para obter um exemplo, consulte Hospedar e
implantar ASP.NET Core Blazor WebAssembly.

Cabeçalhos de controle no código C#


Controlar cabeçalhos na inicialização no código C# usando as abordagens a seguir.

Nos exemplos a seguir, uma CSP (Política de Segurança de Conteúdo) é aplicada ao


aplicativo por meio de um cabeçalho CSP. O {POLICY STRING} espaço reservado é a
cadeia de caracteres de política CSP.

Em Blazor Server aplicativos pré-geradosBlazor WebAssembly, use ASP.NET Core


Middleware para controlar a coleção de cabeçalhos.

Em Program.cs :

C#

app.Use(async (context, next) =>


{
context.Response.Headers.Add("Content-Security-Policy", "{POLICY
STRING}");
await next();
});

O exemplo anterior usa middleware embutido, mas você também pode criar uma
classe de middleware personalizada e chamar o middleware com um método de
extensão no Program.cs . Para obter mais informações, confira Escrever middleware
do ASP.NET Core personalizado.

Em aplicativos hospedados Blazor WebAssembly que não são pré-gerados, passe


StaticFileOptions para MapFallbackToFile que especifique cabeçalhos de resposta
no OnPrepareResponse estágio.

No Program.cs do Server projeto:

C#

var staticFileOptions = new StaticFileOptions


{
OnPrepareResponse = context =>
{
context.Context.Response.Headers.Add("Content-Security-Policy",
"{POLICY STRING}");
}
};

...

app.MapFallbackToFile("index.html", staticFileOptions);

Para obter mais informações sobre CSPs, consulte Impor uma política de segurança de
conteúdo para ASP.NET Core Blazor.

Carregando indicadores de progresso


Esta seção só se aplica a Blazor WebAssembly aplicativos.

O Blazor WebAssembly modelo de projeto contém gráficos vetoriais escalonáveis (SVG)


e indicadores de texto que mostram o progresso do carregamento do aplicativo.

Os indicadores de progresso são implementados com HTML e CSS usando duas


propriedades personalizadas CSS (variáveis) fornecidas por Blazor WebAssembly:

--blazor-load-percentage : o percentual de arquivos de aplicativo carregados.

--blazor-load-percentage-text : o percentual de arquivos de aplicativo carregados,


arredondados para o número inteiro mais próximo.

Usando as variáveis CSS anteriores, você pode criar indicadores de progresso


personalizados que correspondam ao estilo do seu aplicativo.

No exemplo a seguir:

resourcesLoaded é uma contagem instantânea dos recursos carregados durante a


inicialização do aplicativo.
totalResources é o número total de recursos a serem carregados.

JavaScript

const percentage = resourcesLoaded / totalResources * 100;


document.documentElement.style.setProperty(
'--blazor-load-percentage', `${percentage}%`);
document.documentElement.style.setProperty(
'--blazor-load-percentage-text', `"${Math.floor(percentage)}%"`);

O indicador de progresso redondo padrão é implementado em HTML no


wwwroot/index.html arquivo :

HTML

<div id="app">
<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>
</div>

Para examinar a marcação e o estilo do Blazor WebAssembly modelo de projeto para os


indicadores de progresso padrão, consulte o ASP.NET Core fonte de referência:

wwwroot/index.html
app.css

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Em vez de usar o indicador de progresso redondo padrão, o exemplo a seguir mostra


como implementar um indicador de progresso linear.

Adicione os seguintes estilos a wwwroot/css/app.css :

css

.linear-progress {
background: silver;
width: 50vw;
margin: 20% auto;
height: 1rem;
border-radius: 10rem;
overflow: hidden;
position: relative;
}

.linear-progress:after {
content: '';
position: absolute;
inset: 0;
background: blue;
scale: var(--blazor-load-percentage, 0%) 100%;
transform-origin: left top;
transition: scale ease-out 0.5s;
}

Uma variável CSS ( var(...) ) é usada para passar o valor de --blazor-load-percentage


para a scale propriedade de um pseudo-elemento azul que indica o progresso do
carregamento dos arquivos do aplicativo. Conforme o aplicativo é carregado, --blazor-
load-percentage é atualizado automaticamente, o que altera dinamicamente a

representação visual do indicador de progresso.

No wwwroot/index.html , remova o indicador de rodada SVG padrão em <div


id="app">...</div> e substitua-o pela seguinte marcação:
HTML

<div class="linear-progress"></div>

Recursos adicionais
Ambientes: definir o ambiente do aplicativo
SignalR
Blazor Inicialização
Configurar tempos limite e Keep-Alive no cliente
Configurar o log do cliente (Blazor Server)
Configurar o log do cliente (Blazor WebAssembly)
Modificar o manipulador de reconexão
Ajustar a contagem e o intervalo de repetição de reconexão
Desconectar o Blazor circuito do cliente
Globalização e localização: defina estaticamente a cultura com Blazor.start() (Blazor
WebAssembly somente)
JS interoperabilidade: injetar um script após Blazor o início
Hospedar e implantar: Blazor WebAssembly: Compactação
Ambientes Blazor do ASP.NET Core
Artigo • 28/11/2022 • 16 minutos para o fim da leitura

Este artigo explica ASP.NET Core ambientes no Blazor, incluindo como definir o
ambiente.

) Importante

Este tópico aplica-se ao Blazor WebAssembly. Para obter diretrizes gerais sobre
ASP.NET Core configuração de aplicativo, que descreve as abordagens a serem
usadas para Blazor Server aplicativos, consulte Usar vários ambientes no ASP.NET
Core.

Para Blazor Server configuração de aplicativos para arquivos estáticos em


ambientes diferentes do ambiente durante o Development desenvolvimento e
teste (por exemplo, Staging), consulte ASP.NET Core Blazor arquivos estáticos.

Ao executar um aplicativo localmente, o ambiente usa como padrão Development .


Quando o aplicativo é publicado, o ambiente usa como padrão Production .

O ambiente é definido usando qualquer uma das seguintes abordagens:

Blazor iniciar configuração


Blazor-Environment Cabeçalho
Serviço de Aplicativo do Azure

O aplicativo do lado Blazor do cliente (Client) de uma solução hospedada Blazor


WebAssembly determina o ambiente do Server aplicativo da solução por meio de um
middleware que comunica o ambiente ao navegador. O Server aplicativo adiciona um
cabeçalho chamado Blazor-Environment com o ambiente como o valor do cabeçalho. O
Client aplicativo lê o cabeçalho e define o ambiente quando o WebAssemblyHost é
criado em Program.cs (WebAssemblyHostBuilder.CreateDefault). O Server aplicativo da
solução é um aplicativo ASP.NET Core, portanto, mais informações sobre como
configurar o ambiente são encontradas em Usar vários ambientes no ASP.NET Core.

Para um aplicativo autônomo Blazor WebAssembly em execução localmente, o servidor


de desenvolvimento adiciona o Blazor-Environment cabeçalho para especificar o
Development ambiente.
Definir o ambiente por meio da configuração
de inicialização
O exemplo a seguir começará Blazor no ambiente se o nome do host incluir
localhost . Staging Caso contrário, o ambiente será definido Production como .

Dentro da marca de fechamento </body> de wwwroot/index.html :

CSHTML

<script src="_framework/blazor.webassembly.js" autostart="false"></script>


<script>
if (window.location.hostname.includes("localhost")) {
Blazor.start({
environment: "Staging"
});
} else {
Blazor.start({
environment: "Production"
});
}
</script>

O uso da environment propriedade substitui o ambiente definido pelo Blazor-


Environment cabeçalho .

Para obter mais informações sobre a inicialização de Blazor, veja Inicialização de Blazor
no ASP.NET Core.

Definir o ambiente por meio do cabeçalho


Para especificar o ambiente para outros ambientes de hospedagem, adicione o Blazor-
Environment cabeçalho .

No exemplo a seguir para o IIS, o cabeçalho personalizado ( Blazor-Environment ) é


adicionado ao arquivo publicado web.config . O web.config arquivo está localizado na
bin/Release/{TARGET FRAMEWORK}/publish pasta , onde o espaço reservado {TARGET
FRAMEWORK} é a estrutura de destino:

XML

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<system.webServer>
...

<httpProtocol>
<customHeaders>
<add name="Blazor-Environment" value="Staging" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>

7 Observação

Para usar um arquivo personalizado web.config para o IIS que não é substituído
quando o aplicativo é publicado na publish pasta, consulte Hospedar e implantar
ASP.NET Core Blazor WebAssembly.

Definir o ambiente para Serviço de Aplicativo


do Azure
As diretrizes nesta seção exigem o uso de um aplicativo hospedado Blazor
WebAssembly .

7 Observação

Para aplicativos Webassembly autônomosBlazor, defina o ambiente manualmente


por meio da configuração de início ou do Blazor-Environment cabeçalho.

Use as seguintes diretrizes para soluções hospedadas hospedadas Blazor WebAssembly


pelo Serviço de Aplicativo do Azure:

1. Confirme se o uso de maiúsculas e minúsculas de segmentos de ambiente em


nomes de arquivo de configurações de aplicativo corresponde exatamente ao uso
de maiúsculas e minúsculas do nome do ambiente. Por exemplo, o nome de
arquivo de configurações de aplicativo correspondentes para o Staging ambiente
é appsettings.Staging.json . Se o nome do arquivo for appsettings.staging.json
(minúsculo " s "), o arquivo não estará localizado e as configurações no arquivo não
serão usadas no Staging ambiente.

2. Na portal do Azure do slot de implantação do ambiente, defina o ambiente com a


configuração do ASPNETCORE_ENVIRONMENT aplicativo. Para um aplicativo chamado
BlazorAzureAppSample , o slot de Serviço de Aplicativo de preparo é chamado
BlazorAzureAppSample/Staging . Para a Staging configuração do slot, crie uma

configuração de aplicativo para ASPNETCORE_ENVIRONMENT com um valor de Staging .


A configuração do slot de implantação está habilitada para a configuração.

3. Para a implantação do Visual Studio, confirme se o aplicativo está implantado no


slot de implantação correto. Para um aplicativo chamado BlazorAzureAppSample , o
aplicativo é implantado no Staging slot de implantação.

Quando solicitado em um navegador, o BlazorAzureAppSample/Staging aplicativo é


carregado no Staging ambiente em https://blazorazureappsample-
staging.azurewebsites.net .

Quando o aplicativo é carregado no navegador, a coleção de cabeçalhos de resposta


para blazor.boot.json indica que o valor do Blazor-Environment cabeçalho é Staging .

As configurações do aplicativo do appsettings.{ENVIRONMENT}.json arquivo são


carregadas pelo aplicativo, em que o {ENVIRONMENT} espaço reservado é o ambiente do
aplicativo. No exemplo anterior, as configurações do appsettings.Staging.json arquivo
são carregadas.

Ler o ambiente
Obtenha o ambiente do aplicativo em um componente injetando
IWebAssemblyHostEnvironment e lendo a Environment propriedade .

Pages/ReadEnvironment.razor :

razor

@page "/read-environment"
@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
@inject IWebAssemblyHostEnvironment HostEnvironment

<h1>Environment example</h1>

<p>Environment: @HostEnvironment.Environment</p>

Durante a inicialização, o WebAssemblyHostBuilder expõe o


IWebAssemblyHostEnvironment por meio da propriedade , que habilita a
HostEnvironment lógica específica do ambiente no código do construtor de host.

Em Program.cs :

C#
if (builder.HostEnvironment.Environment == "Custom")
{
...
};

Os seguintes métodos de extensão de conveniência fornecidos por meio


WebAssemblyHostEnvironmentExtensions da permissão de verificação do ambiente
atual para Development , Production , Staging e nomes de ambiente personalizados:

IsDevelopment
IsProduction
IsStaging
IsEnvironment

Em Program.cs :

C#

if (builder.HostEnvironment.IsStaging())
{
...
};

if (builder.HostEnvironment.IsEnvironment("Custom"))
{
...
};

A IWebAssemblyHostEnvironment.BaseAddress propriedade pode ser usada durante a


inicialização quando o NavigationManager serviço não está disponível.

Recursos adicionais
inicialização Blazor do ASP.NET Core
Usar múltiplos ambientes no ASP.NET Core
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Blazor ASP.NET Core registro em log
Artigo • 10/01/2023 • 51 minutos para o fim da leitura

Este artigo explica como fazer logon em Blazor aplicativos, incluindo configuração e
como gravar mensagens de log de Razor componentes.

Configuração
A configuração de log pode ser carregada de arquivos de configurações de aplicativo.
Para obter mais informações, consulte ASP.NET Core Blazor configuração.

Em níveis de log padrão e sem configurar provedores de log adicionais:

Blazor Server os aplicativos só fazem logon no console do .NET do lado do


servidor no Development ambiente no LogLevel.Information nível ou superior.
Blazor WebAssembly os aplicativos só fazem logon no console de ferramentas de
desenvolvedor do navegador do lado do cliente no LogLevel.Information nível
ou superior.

Quando o aplicativo é configurado no arquivo de projeto para usar namespaces


implícitos ( <ImplicitUsings>enable</ImplicitUsings> ), uma using diretiva para
Microsoft.Extensions.Logging ou qualquer API na LoggerExtensions classe não é
necessária para dar suporte a conclusões do API Visual Studio IntelliSense ou criação de
aplicativos. Se os namespaces implícitos não estiverem habilitados, Razor os
componentes deverão definir @using explicitamente diretivas para registrar
namespaces que não são importados por meio do _Imports.razor arquivo.

Níveis de log
Os níveis de log em aplicativos estão em Blazor conformidade com ASP.NET Core níveis
de log do aplicativo, que estão listados na documentação da API em LogLevel.

Razor registro em log de componentes


O exemplo a seguir:

Injeta um ILogger objeto ( ILogger<Counter> ) para criar um agente. A categoria do


log é o nome totalmente qualificado do tipo do componente, Counter .
Chama LogWarning para registrar em log no nível Warning.
Pages/Counter1.razor :

razor

@page "/counter-1"
@inject ILogger<Counter> logger

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
logger.LogWarning("Someone has clicked me!");

currentCount++;
}
}

O exemplo a seguir demonstra o registro em log com um ILoggerFactory em


componentes.

Pages/Counter2.razor :

razor

@page "/counter-2"
@inject ILoggerFactory LoggerFactory

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
var logger = LoggerFactory.CreateLogger<Counter>();
logger.LogWarning("Someone has clicked me!");

currentCount++;
}
}
Fazer logon em Blazor Server aplicativos
Para obter diretrizes gerais de registro em log ASP.NET Core referentes ao Blazor Server,
consulte Registro em log no .NET Core e ASP.NET Core.

Fazer logon em Blazor WebAssembly


aplicativos
Nem todos os recursos de ASP.NET Core registro em log têm suporte em Blazor
WebAssembly aplicativos. Por exemplo, Blazor WebAssembly os aplicativos não têm
acesso ao sistema de arquivos ou à rede do cliente, portanto, não é possível gravar logs
no armazenamento físico ou de rede do cliente. Ao usar um serviço de log de terceiros
projetado para funcionar com SPAs (aplicativos de página única), siga as diretrizes de
segurança do serviço. Tenha em mente que todos os dados, incluindo chaves ou
segredos armazenados no Blazor WebAssembly aplicativo, são inseguros e podem ser
facilmente descobertos por usuários mal-intencionados.

Configure o registro em log em Blazor WebAssembly aplicativos com a


WebAssemblyHostBuilder.Logging propriedade . A Logging propriedade é do tipo
ILoggingBuilder, portanto, os métodos de extensão de ILoggingBuilder têm suporte.

Para definir o nível mínimo de log, chame LoggingBuilderExtensions.SetMinimumLevel


no construtor de host em Program.cs com o LogLevel. O exemplo a seguir define o nível
mínimo de log como Warning:

C#

builder.Logging.SetMinimumLevel(LogLevel.Warning);

Fazer logon Program.cs (Blazor WebAssembly)


Fazendo logon Program.cs tem suporte em Blazor WebAssembly aplicativos depois que
o WebAssemblyHostBuilder é criado usando o provedor interno do agente de console
da estrutura (WebAssemblyConsoleLoggerProvider (origem de referência) ).

Em Program.cs :

C#

var host = builder.Build();

var logger = host.Services.GetRequiredService<ILoggerFactory>()


.CreateLogger<Program>();

logger.LogInformation("Logged after the app is built in Program.cs.");

await host.RunAsync();

Ferramentas de desenvolvedor saída do console:

info: Program[0]
Logged after the app is built in Program.cs.

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Categoria de log (Blazor WebAssembly)


Há suporte para categorias de log em Blazor WebAssembly aplicativos.

O exemplo a seguir mostra como usar categorias de log com o Counter componente de
um aplicativo criado com base em um Blazor modelo de projeto.

IncrementCount No método do componente do Counter aplicativo

( Pages/Counter.razor ) que injeta um ILoggerFactory como LoggerFactory :

C#

var logger = LoggerFactory.CreateLogger("CustomCategory");


logger.LogWarning("Someone has clicked me!");

Ferramentas de desenvolvedor saída do console:

warn: CustomCategory[0]
Someone has clicked me!

ID do evento de log (Blazor WebAssembly)


Há suporte para a ID do evento de log em Blazor WebAssembly aplicativos.

O exemplo a seguir mostra como usar IDs de evento de log com o Counter
componente de um aplicativo criado com base em um Blazor modelo de projeto.

LogEvent.cs :

C#

public class LogEvent


{
public const int Event1 = 1000;
public const int Event2 = 1001;
}

IncrementCount No método do componente do Counter aplicativo

( Pages/Counter.razor ):

C#

logger.LogInformation(LogEvent.Event1, "Someone has clicked me!");


logger.LogWarning(LogEvent.Event2, "Someone has clicked me!");

Ferramentas de desenvolvedor saída do console:

info: BlazorSample.Pages.Counter[1000]
Someone has clicked me!
warn: BlazorSample.Pages.Counter[1001]
Someone has clicked me!

Modelo de mensagem de log (Blazor


WebAssembly)
Há suporte para modelos de mensagem de log em Blazor WebAssembly aplicativos:

O exemplo a seguir mostra como usar modelos de mensagem de log com o Counter
componente de um aplicativo criado a partir de um Blazor modelo de projeto.

IncrementCount No método do componente do Counter aplicativo

( Pages/Counter.razor ):

C#
logger.LogInformation("Someone clicked me at {CurrentDT}!",
DateTime.UtcNow);

Ferramentas de desenvolvedor saída do console:

info: BlazorSample.Pages.Counter[0]
Someone clicked me at 04/21/2022 12:15:57!

Parâmetros de exceção de log (Blazor


WebAssembly)
Há suporte para parâmetros de exceção de log em Blazor WebAssembly aplicativos.

O exemplo a seguir mostra como usar parâmetros de exceção de log com o Counter
componente de um aplicativo criado a partir de um Blazor modelo de projeto.

IncrementCount No método do componente do Counter aplicativo


( Pages/Counter.razor ):

C#

currentCount++;

try
{
if (currentCount == 3)
{
currentCount = 4;
throw new OperationCanceledException("Skip 3");
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Exception (currentCount: {Count})!",
currentCount);
}

Ferramentas de desenvolvedor saída do console:

warn: BlazorSample.Pages.Counter[0]
Exception (currentCount: 4)!
System.OperationCanceledException: Skip 3
at BlazorSample.Pages.Counter.IncrementCount() in
C:UsersAlabaDesktopBlazorSamplePagesCounter.razor:line 28
Função Filter (Blazor WebAssembly)
Há suporte para funções de filtro em Blazor WebAssembly aplicativos.

O exemplo a seguir mostra como usar um filtro com o Counter componente de um


aplicativo criado com base em um Blazor modelo de projeto.

Em Program.cs :

C#

builder.Logging.AddFilter((provider, category, logLevel) =>


category.Equals("CustomCategory2") && logLevel == LogLevel.Information);

IncrementCount No método do componente do Counter aplicativo


( Pages/Counter.razor ) que injeta um ILoggerFactory como LoggerFactory :

C#

var logger1 = LoggerFactory.CreateLogger("CustomCategory1");


logger1.LogInformation("Someone has clicked me!");

var logger2 = LoggerFactory.CreateLogger("CustomCategory1");


logger2.LogWarning("Someone has clicked me!");

var logger3 = LoggerFactory.CreateLogger("CustomCategory2");


logger3.LogInformation("Someone has clicked me!");

var logger4 = LoggerFactory.CreateLogger("CustomCategory2");


logger4.LogWarning("Someone has clicked me!");

Na saída do console de ferramentas de desenvolvedor, o filtro só permite o registro em


log para a categoria e Information a CustomCategory2 mensagem de nível de log:

info: CustomCategory2[0]
Someone has clicked me!

O aplicativo também pode configurar a filtragem de log para namespaces específicos.


Por exemplo, defina o nível de log como Trace em Program.cs :

C#

builder.Logging.SetMinimumLevel(LogLevel.Trace);
Normalmente, no nível do log, a saída do Trace console de ferramentas de
desenvolvedor no nível Detalhado inclui Microsoft.AspNetCore.Components.RenderTree
mensagens de log, como as seguintes:

dbug: Microsoft.AspNetCore.Components.RenderTree.Renderer[3]
Rendering component 14 of type
Microsoft.AspNetCore.Components.Web.HeadOutlet

No Program.cs , o registro em log de mensagens específicas a


Microsoft.AspNetCore.Components.RenderTree pode ser desabilitado usando uma das
seguintes abordagens:

C#

builder.Logging.AddFilter("Microsoft.AspNetCore.Components.RenderTree.*
", LogLevel.None);

C#

builder.Services.PostConfigure<LoggerFilterOptions>(options =>
options.Rules.Add(
new LoggerFilterRule(null,

"Microsoft.AspNetCore.Components.RenderTree.*",
LogLevel.None,
null)
));

Depois que um dos filtros anteriores é adicionado ao aplicativo, a saída do console no


nível Detalhado não mostra mensagens de log da
Microsoft.AspNetCore.Components.RenderTree API.

Provedor de agente personalizado (Blazor


WebAssembly)
O exemplo nesta seção demonstra um provedor de agente personalizado para
personalização adicional.

Adicione uma referência de pacote ao aplicativo para o


Microsoft.Extensions.Logging.Configuration pacote.

7 Observação
Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Adicione a seguinte configuração de agente personalizado. A configuração estabelece


um LogLevels dicionário que define um formato de log personalizado para três níveis
de log: Information, Warninge Error. Um LogFormat enum é usado para descrever
formatos curtos ( LogFormat.Short ) e longos ( LogFormat.Long ).

CustomLoggerConfiguration.cs :

C#

using Microsoft.Extensions.Logging;

public class CustomLoggerConfiguration


{
public int EventId { get; set; }

public Dictionary<LogLevel, LogFormat> LogLevels { get; set; } =


new()
{
[LogLevel.Information] = LogFormat.Short,
[LogLevel.Warning] = LogFormat.Short,
[LogLevel.Error] = LogFormat.Long
};

public enum LogFormat


{
Short,
Long
}
}

Adicione o seguinte agente personalizado ao aplicativo. O CustomLogger gera formatos


de log personalizados com base nos logLevel valores definidos na configuração
anterior CustomLoggerConfiguration .

C#

using Microsoft.Extensions.Logging;
using static CustomLoggerConfiguration;

public sealed class CustomLogger : ILogger


{
private readonly string name;
private readonly Func<CustomLoggerConfiguration> getCurrentConfig;
public CustomLogger(
string name,
Func<CustomLoggerConfiguration> getCurrentConfig) =>
(this.name, this.getCurrentConfig) = (name, getCurrentConfig);

public IDisposable BeginScope<TState>(TState state) => default!;

public bool IsEnabled(LogLevel logLevel) =>


getCurrentConfig().LogLevels.ContainsKey(logLevel);

public void Log<TState>(


LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}

CustomLoggerConfiguration config = getCurrentConfig();

if (config.EventId == 0 || config.EventId == eventId.Id)


{
switch (config.LogLevels[logLevel])
{
case LogFormat.Short:
Console.WriteLine($"{name}: {formatter(state,
exception)}");
break;
case LogFormat.Long:
Console.WriteLine($"[{eventId.Id, 2}: {logLevel, -12}]
{name} - {formatter(state, exception)}");
break;
default:
// No-op
break;
}
}
}
}

Adicione o seguinte provedor de agente personalizado ao aplicativo.


CustomLoggerProvider adota uma Optionsabordagem baseada em para configurar o

agente por meio de recursos internos de configuração de log. Por exemplo, o aplicativo
pode definir ou alterar formatos de log por meio de um appsettings.json arquivo sem
exigir alterações de código no agente personalizado, o que é demonstrado no final
desta seção.
CustomLoggerProvider.cs :

C#

using System.Collections.Concurrent;
using Microsoft.Extensions.Options;

[ProviderAlias("CustomLog")]
public sealed class CustomLoggerProvider : ILoggerProvider
{
private readonly IDisposable onChangeToken;
private CustomLoggerConfiguration config;
private readonly ConcurrentDictionary<string, CustomLogger> loggers =
new(StringComparer.OrdinalIgnoreCase);

public CustomLoggerProvider(
IOptionsMonitor<CustomLoggerConfiguration> config)
{
this.config = config.CurrentValue;
onChangeToken = config.OnChange(updatedConfig => this.config =
updatedConfig);
}

public ILogger CreateLogger(string categoryName) =>


loggers.GetOrAdd(categoryName, name => new CustomLogger(name,
GetCurrentConfig));

private CustomLoggerConfiguration GetCurrentConfig() => config;

public void Dispose()


{
loggers.Clear();
onChangeToken.Dispose();
}
}

Adicione as seguintes extensões de agente personalizado.

CustomLoggerExtensions.cs :

C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

public static class CustomLoggerExtensions


{
public static ILoggingBuilder AddCustomLogger(
this ILoggingBuilder builder)
{
builder.AddConfiguration();

builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider,
CustomLoggerProvider>());

LoggerProviderOptions.RegisterProviderOptions
<CustomLoggerConfiguration, CustomLoggerProvider>
(builder.Services);

return builder;
}
}

No Program.cs construtor de host, limpe o provedor existente chamando ClearProviders


e adicione o provedor de log personalizado:

C#

builder.Logging.ClearProviders().AddCustomLogger();

No seguinte componente Index :

A mensagem de depuração não está registrada.


A mensagem de informações é registrada no formato curto ( LogFormat.Short ).
A mensagem de aviso é registrada no formato curto ( LogFormat.Short ).
A mensagem de erro é registrada no formato longo ( LogFormat.Long ).
A mensagem de rastreamento não é registrada.

Pages/Index.razor :

razor

@page "/"
@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger

<p>
<button @onclick="LogMessages">Log Messages</button>
</p>

@code{
private void LogMessages()
{
Logger.LogDebug(1, "This is a debug message.");
Logger.LogInformation(3, "This is an information message.");
Logger.LogWarning(5, "This is a warning message.");
Logger.LogError(7, "This is an error message.");
Logger.LogTrace(5!, "This is a trace message.");
}
}

A saída a seguir é vista no console de ferramentas para desenvolvedores do navegador


quando o Log Messages botão é selecionado. As entradas de log refletem os formatos
apropriados aplicados pelo agente personalizado:

LoggingTest.Pages.Index: This is an information message.


LoggingTest.Pages.Index: This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

A partir de uma inspeção casual do exemplo anterior, é evidente que a configuração dos
formatos de linha de log por meio do dicionário em CustomLoggerConfiguration não é
estritamente necessária. Os formatos de linha aplicados pelo agente personalizado
( CustomLogger ) poderiam ter sido aplicados apenas verificando o logLevel no Log
método . A finalidade de atribuir o formato de log por meio da configuração é que o
desenvolvedor pode alterar o formato de log facilmente por meio da configuração do
aplicativo, como demonstra o exemplo a seguir.

wwwroot Na pasta , adicione ou atualize o appsettings.json arquivo para incluir a


configuração de log. Defina o formato Long de log como para todos os três níveis de
log:

JSON

{
"Logging": {
"CustomLog": {
"LogLevels": {
"Information": "Long",
"Warning": "Long",
"Error": "Long"
}
}
}
}

No exemplo anterior, observe que a entrada para a configuração do agente


personalizado é CustomLog , que foi aplicada ao provedor de agente personalizado
( CustomLoggerProvider ) como um alias com [ProviderAlias("CustomLog")] . A
configuração de log poderia ter sido aplicada com o nome CustomLoggerProvider em
vez de CustomLog , mas o uso do alias CustomLog é mais amigável.

Em Program.cs , consuma a configuração de log. Adicione os códigos a seguir:


C#

builder.Logging.AddConfiguration(
builder.Configuration.GetSection("Logging"));

A chamada para LoggingBuilderConfigurationExtensions.AddConfiguration pode ser


colocada antes ou depois de adicionar o provedor de agente personalizado.

Execute o aplicativo novamente. Selecione o botão Log Messages . Observe que a


configuração de log é aplicada do appsettings.json arquivo. Todas as três entradas de
log estão no formato longo ( LogFormat.Long ):

[ 3: Information ] LoggingTest.Pages.Index - This is an information message.


[ 5: Warning ] LoggingTest.Pages.Index - This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

Escopos de log (Blazor WebAssembly)


O Blazor WebAssembly agente de console de ferramentas de desenvolvedor não dá
suporte a escopos de log. No entanto, um agente personalizado pode dar suporte a
escopos de log. Para obter um exemplo sem suporte que você pode desenvolver ainda
mais para atender às suas necessidades, consulte o protótipo no dotnet/blazor-samples
repositório GitHub:

BlazorWebAssemblyScopesLogger aplicativo de exemplo

O aplicativo de exemplo usa a sintaxe de log padrão ASP.NET Core BeginScope para
indicar escopos para mensagens registradas. O Logger serviço no exemplo a seguir é
um ILogger<Index> , que é injetado no componente do Index aplicativo
( Pages/Index.razor ).

C#

using (Logger.BeginScope("L1"))
{
Logger.LogInformation(3, "INFO: ONE scope.");
}

using (Logger.BeginScope("L1"))
{
using (Logger.BeginScope("L2"))
{
Logger.LogInformation(3, "INFO: TWO scopes.");
}
}
using (Logger.BeginScope("L1"))
{
using (Logger.BeginScope("L2"))
{
using (Logger.BeginScope("L3"))
{
Logger.LogInformation(3, "INFO: THREE scopes.");
}
}
}

Saída:

[ 3: Informações ] ScopesLogger.Pages.Index - INFO: one scope. => L1


blazor.webassembly.js:1:35542
[ 3: Informações ] ScopesLogger.Pages.Index - INFO: DOIS escopos. => L1 => L2
blazor.webassembly.js:1:35542
[ 3: Informações ] ScopesLogger.Pages.Index - INFO: TRÊS escopos. => L1 => L2 =>
L3

Registro em log hospedado Blazor


WebAssembly
Um aplicativo hospedado Blazor WebAssembly que pré-remete seu conteúdo executa o
código de inicialização do componente duas vezes. O registro em log ocorre no lado do
servidor na primeira execução do código de inicialização e do lado do cliente na
segunda execução do código de inicialização. Dependendo da meta de registro em log
durante a inicialização, verifique os logs do lado do servidor, do lado do cliente ou
ambos.

SignalR log do cliente (Blazor Server)


No construtor de cliente no Pages/_Layout.cshtml , passe o configureSignalR objeto de
configuração que chama configureLogging com o nível de log.

Para o valor de configureLogging nível de log, passe o argumento como o nível de log
de cadeia de caracteres ou inteiro mostrado na tabela a seguir.

LogLevel Configuração de cadeia de caracteres Configuração de inteiro

Trace trace 0
LogLevel Configuração de cadeia de caracteres Configuração de inteiro

Debug debug 1

Information information 2

Warning warning 3

Error error 4

Critical critical 5

None none 6

Exemplo 1: definir o nível de Information log com um valor de cadeia de caracteres:

HTML

<script src="_framework/blazor.server.js" autostart="false"></script>


<script>
Blazor.start({
configureSignalR: function (builder) {
builder.configureLogging("information");
}
});
</script>

Exemplo 2: definir o nível de Information log com um valor inteiro:

HTML

<script src="_framework/blazor.server.js" autostart="false"></script>


<script>
Blazor.start({
configureSignalR: function (builder) {
builder.configureLogging(2);
}
});
</script>

Para obter mais informações sobre Blazor a inicialização ( Blazor.start() ), consulte


ASP.NET Core Blazor inicialização.

SignalR log do cliente (Blazor WebAssembly)


Em Blazor WebAssembly aplicativos, defina a configuração de configurações de
aplicativo conforme descrito em ASP.NET Core Blazor configuração. Coloque arquivos
de configurações de aplicativo no wwwroot que contêm uma
Logging:LogLevel:HubConnection configuração de aplicativo.

7 Observação

Como alternativa ao uso de configurações de aplicativo, você pode passar o


LogLevel como o argumento para LoggingBuilderExtensions.SetMinimumLevel
quando a conexão de hub é criada em um Razor componente. No entanto,
implantar acidentalmente o aplicativo em um ambiente de hospedagem de
produção com log detalhado pode resultar em uma penalidade de desempenho. É
recomendável usar as configurações do aplicativo para definir o nível de log.

Forneça uma Logging:LogLevel:HubConnection configuração de aplicativo no arquivo


padrão appsettings.json e no arquivo de configurações do aplicativo de Development
ambiente. Use um nível de log menos detalhado típico para o padrão, como
LogLevel.Warning. O valor de configurações de aplicativo padrão é o que é usado em
Staging ambientes e Production se nenhum arquivo de configurações de aplicativo
para esses ambientes estiver presente. Use um nível de log detalhado no arquivo de
configurações do aplicativo de Development ambiente, como LogLevel.Trace.

wwwroot/appsettings.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"HubConnection": "Warning"
}
}
}

wwwroot/appsettings.Development.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"HubConnection": "Trace"
}
}
}

) Importante

A configuração nos arquivos de configurações de aplicativo anteriores só será


usada pelo aplicativo se as diretrizes em ASP.NET Core Blazor configuração forem
seguidas.

Na parte superior do Razor arquivo de componente ( .razor ):

Injete um ILoggerProvider para adicionar um WebAssemblyConsoleLogger aos


provedores de log passados para HubConnectionBuilder. Ao contrário de um
tradicional ConsoleLogger, WebAssemblyConsoleLogger é um wrapper em torno de
APIs de log específicas do navegador (por exemplo, console.log ). O uso de
possibilita o registro em log no Mono dentro de um contexto de
WebAssemblyConsoleLogger navegador.

Injete um IConfiguration para ler a configuração do


Logging:LogLevel:HubConnection aplicativo.

7 Observação

WebAssemblyConsoleLogger é interno e não tem suporte para uso direto no código

do desenvolvedor.

C#

@inject ILoggerProvider LoggerProvider


@inject IConfiguration Config

7 Observação

O exemplo a seguir baseia-se no Index componente no SignalR tutorial com


Blazor. Consulte o tutorial para obter mais detalhes.

No método do OnInitializedAsynccomponente, use


HubConnectionBuilderExtensions.ConfigureLogging para adicionar o provedor de log e
definir o nível mínimo de log da configuração:

C#
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.ConfigureLogging(builder =>
{
builder.AddProvider(LoggerProvider);
builder.SetMinimumLevel(
Config.GetValue<LogLevel>
("Logging:LogLevel:HubConnection"));
})
.Build();

hubConnection.On<string, string>("ReceiveMessage", (user, message) =>


...

await hubConnection.StartAsync();
}

7 Observação

No exemplo anterior, Navigation é um injetado NavigationManager.

Para obter mais informações sobre como definir o ambiente do aplicativo para Blazor
WebAssembly, consulte ambientes ASP.NET CoreBlazor.

Log de autenticação (Blazor WebAssembly)


Esta seção só se aplica a Blazor WebAssembly aplicativos no ASP.NET Core 7.0 ou
posterior.

Registre Blazor mensagens de autenticação nos LogLevel.Debug níveis de log ou


LogLevel.Trace com uma configuração de log nas configurações do aplicativo ou usando
um filtro de log para Microsoft.AspNetCore.Components.WebAssembly.Authentication
no Program.cs .

Use uma das seguintes abordagens:

Em um arquivo de configurações do aplicativo (por exemplo,


wwwroot/appsettings.Development.json ):

JSON

"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Components.WebAssembly.Authentication":
"Debug"
}
}

Para obter mais informações sobre como habilitar um Blazor WebAssembly


aplicativo para ler arquivos de configurações de aplicativo, consulte ASP.NET Core
Blazor configuração.

Usando um filtro de log, o seguinte exemplo:


Ativa o registro em log para a configuração de Debug build usando uma diretiva
de pré-processador C#.
Registra mensagens Blazor de autenticação no nível do Debug log.

C#

#if DEBUG
builder.Logging.AddFilter(
"Microsoft.AspNetCore.Components.WebAssembly.Authentication",
LogLevel.Debug);
#endif

7 Observação

Blazor WebAssembly os aplicativos só fazem logon no console de ferramentas de


desenvolvedor do navegador do lado do cliente.

Recursos adicionais
Log no .NET Core e no ASP.NET Core
Loglevel Enumeração (documentação da API)
Implementar um provedor de log personalizado no .NET
Documentação das ferramentas de desenvolvedor do navegador:
Chrome DevTools
Ferramentas para Desenvolvedores do Firefox
Visão geral das Ferramentas para Desenvolvedores do Microsoft Edge
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Lidar com erros em aplicativos ASP.NET
Core Blazor
Artigo • 28/11/2022 • 89 minutos para o fim da leitura

Este artigo descreve como Blazor gerencia exceções sem tratamento e como
desenvolver aplicativos que detectam e lidam com erros.

Erros detalhados durante o desenvolvimento


Quando um Blazor aplicativo não está funcionando corretamente durante o
desenvolvimento, receber informações detalhadas de erro do aplicativo ajuda na
solução de problemas e na correção do problema. Quando ocorre um erro, Blazor os
aplicativos exibem uma barra amarela clara na parte inferior da tela:

Durante o desenvolvimento, a barra direciona você para o console do navegador,


onde você pode ver a exceção.
Em produção, a barra notifica o usuário de que ocorreu um erro e recomenda
atualizar o navegador.

A interface do usuário para essa experiência de tratamento de erros faz parte dos
modelos deBlazor projeto.

Em um Blazor Server aplicativo, personalize a experiência no Pages/_Host.cshtml


arquivo:

CSHTML

<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until
reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for
details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

Em um Blazor WebAssembly aplicativo, personalize a experiência no wwwroot/index.html


arquivo:
HTML

<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

O blazor-error-ui elemento normalmente está oculto devido à presença do display:


none estilo da blazor-error-ui classe CSS na folha de estilos do site

( wwwroot/css/site.css para Blazor Server ou wwwroot/css/app.css para Blazor


WebAssembly). Quando ocorre um erro, a estrutura se aplica display: block ao
elemento .

Erros detalhados de circuito


Esta seção se aplica a Blazor Server aplicativos.

Os erros do lado do cliente não incluem a pilha de chamadas e não fornecem detalhes
sobre a causa do erro, mas os logs do servidor contêm essas informações. Para fins de
desenvolvimento, informações confidenciais de erro de circuito podem ser
disponibilizadas para o cliente habilitando erros detalhados.

Defina CircuitOptions.DetailedErrors como true . Para obter mais informações e um


exemplo, consulte ASP.NET Core BlazorSignalR diretrizes.

Uma alternativa à configuração CircuitOptions.DetailedErrors é definir a DetailedErrors


chave de configuração como true no arquivo de configurações do ambiente de
desenvolvimento do aplicativo ( appsettings.Development.json ). Além disso, defina
SignalR o log do lado do servidor ( Microsoft.AspNetCore.SignalR ) como Depurar ou
Rastrear para registro em log detalhado SignalR .

appsettings.Development.json :

JSON

{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}

A DetailedErrors chave de configuração também pode ser definida como true usando a
ASPNETCORE_DETAILEDERRORS variável de ambiente com um valor de em servidores de
true ambiente de desenvolvimento/preparo ou em seu sistema local.

2 Aviso

Sempre evite expor informações de erro a clientes na Internet, o que é um risco à


segurança.

Gerenciar exceções sem tratamento no código


do desenvolvedor
Para que um aplicativo continue após um erro, o aplicativo deve ter lógica de
tratamento de erros. Seções posteriores deste artigo descrevem possíveis fontes de
exceções sem tratamento.

Em produção, não renderize mensagens de exceção de estrutura ou rastreamentos de


pilha na interface do usuário. Renderizar mensagens de exceção ou rastreamentos de
pilha pode:

Divulgar informações confidenciais aos usuários finais.


Ajude um usuário mal-intencionado a descobrir pontos fracos em um aplicativo
que podem comprometer a segurança do aplicativo, do servidor ou da rede.

Blazor Server exceções sem tratamento


Esta seção se aplica a Blazor Server aplicativos.

Blazor Server é uma estrutura com estado. Enquanto os usuários interagem com um
aplicativo, eles mantêm uma conexão com o servidor conhecido como circuito. O
circuito contém instâncias de componente ativas, além de muitos outros aspectos do
estado, como:

A saída renderizada mais recente dos componentes.


O conjunto atual de delegados de manipulação de eventos que podem ser
disparados por eventos do lado do cliente.
Se um usuário abrir o aplicativo em várias guias do navegador, o usuário criará vários
circuitos independentes.

Blazor trata a maioria das exceções sem tratamento como fatais para o circuito em que
ocorrem. Se um circuito for encerrado devido a uma exceção sem tratamento, o usuário
só poderá continuar a interagir com o aplicativo recarregando a página para criar um
novo circuito. Circuitos fora daquele que foi encerrado, que são circuitos para outros
usuários ou outras guias do navegador, não são afetados. Esse cenário é semelhante a
um aplicativo da área de trabalho que falha. O aplicativo com falha deve ser reiniciado,
mas outros aplicativos não são afetados.

A estrutura encerra um circuito quando ocorre uma exceção sem tratamento pelos
seguintes motivos:

Uma exceção sem tratamento geralmente deixa o circuito em um estado


indefinido.
A operação normal do aplicativo não pode ser garantida após uma exceção sem
tratamento.
As vulnerabilidades de segurança poderão aparecer no aplicativo se o circuito
continuar em um estado indefinido.

Limites de erro
Blazor é uma estrutura do lado do cliente do aplicativo de página única (SPA). O
navegador serve como host do aplicativo e, portanto, atua como o pipeline de
processamento para componentes individuais Razor com base em solicitações de URI
para navegação e ativos estáticos. Ao contrário ASP.NET Core aplicativos executados no
servidor com um pipeline de processamento de middleware, não há nenhum pipeline
de middleware que processe solicitações de Razor componentes que possam ser
aproveitados para tratamento de erros global. No entanto, um aplicativo pode usar um
componente de processamento de erro como um valor em cascata para processar erros
de maneira centralizada.

Os limites de erro fornecem uma abordagem conveniente para lidar com exceções. O
ErrorBoundary componente:

Renderiza seu conteúdo filho quando um erro não ocorreu.


Renderiza a interface do usuário do erro quando uma exceção sem tratamento é
gerada.

Para definir um limite de erro, use o componente para encapsular o ErrorBoundary


conteúdo existente. Por exemplo, um limite de erro pode ser adicionado ao redor do
conteúdo do corpo do layout principal do aplicativo.

Shared/MainLayout.razor :

razor

<main>
<article class="content px-4">
<ErrorBoundary>
@Body
</ErrorBoundary>
</article>
</main>

O aplicativo continua funcionando normalmente, mas o limite de erro lida com exceções
sem tratamento.

Considere o exemplo a seguir, em que o Counter componente gera uma exceção se a


contagem incrementa mais de cinco.

Em Pages/Counter.razor :

C#

private void IncrementCount()


{
currentCount++;

if (currentCount > 5)
{
throw new InvalidOperationException("Current count is too big!");
}
}

Se a exceção sem tratamento for gerada para mais de currentCount cinco:

A exceção é tratada pelo limite de erro.


A interface do usuário do erro é renderizada ( An error has occurred. ).

Por padrão, o ErrorBoundary componente renderiza um elemento vazio <div> com a


blazor-error-boundary classe CSS para seu conteúdo de erro. As cores, o wwwroot texto

e o ícone da interface do usuário padrão são definidos usando CSS na folha de estilos
do aplicativo na pasta, portanto, você está livre para personalizar a interface do usuário
do erro.

Você também pode alterar o conteúdo de erro padrão definindo a ErrorContent


propriedade :
razor

<ErrorBoundary>
<ChildContent>
@Body
</ChildContent>
<ErrorContent>
<p class="errorUI">Nothing to see here right now. Sorry!</p>
</ErrorContent>
</ErrorBoundary>

Como o limite de erro é definido no layout nos exemplos anteriores, a interface do


usuário de erro é vista independentemente de qual página o usuário navegou.
Recomendamos limitar os limites de erro de escopo na maioria dos cenários. Se você
definir amplamente o escopo de um limite de erro, poderá redefini-lo para um estado
de não erro em eventos de navegação de página subsequentes chamando o método do
limite de Recover erro:

razor

...

<ErrorBoundary @ref="errorBoundary">
@Body
</ErrorBoundary>

...

@code {
private ErrorBoundary? errorBoundary;

protected override void OnParametersSet()


{
errorBoundary?.Recover();
}
}

Tratamento de exceção global alternativo


Uma alternativa ao uso de Limites de erro (ErrorBoundary) é passar um componente de
erro personalizado como um CascadingValue para componentes filho. Uma vantagem
de usar um componente em vez de usar um serviço injetado ou uma implementação de
agente personalizado é que um componente em cascata pode renderizar conteúdo e
aplicar estilos CSS quando ocorrer um erro.
Error O exemplo de componente a seguir apenas registra erros, mas os métodos do

componente podem processar erros de qualquer maneira exigida pelo aplicativo,


inclusive por meio do uso de vários métodos de processamento de erro.

Shared/Error.razor :

razor

@using Microsoft.Extensions.Logging
@inject ILogger<Error> Logger

<CascadingValue Value="this">
@ChildContent
</CascadingValue>

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }

public void ProcessError(Exception ex)


{
Logger.LogError("Error:ProcessError - Type: {Type} Message:
{Message}",
ex.GetType(), ex.Message);
}
}

7 Observação

Para obter mais informações sobre RenderFragment, consulte ASP.NET Core Razor
componentes.

App No componente , encapsule o Router componente com o Error componente . Isso


permite que o Error componente seja reduzido em cascata para qualquer componente
do aplicativo em que o Error componente é recebido como um CascadingParameter.

App.razor :

razor

<Error>
<Router ...>
...
</Router>
</Error>

Para processar erros em um componente:


Designe o Error componente como um CascadingParameter no @code bloco. Em
um componente de exemplo Counter em um aplicativo com base em um Blazor
modelo de projeto, adicione a seguinte Error propriedade:

C#

[CascadingParameter]
public Error? Error { get; set; }

Chame um método de processamento de erro em qualquer catch bloco com um


tipo de exceção apropriado. O componente de exemplo Error oferece apenas um
único ProcessError método, mas o componente de processamento de erros pode
fornecer qualquer número de métodos de processamento de erro para atender
aos requisitos alternativos de processamento de erros em todo o aplicativo. No
exemplo de componente a seguir Counter , uma exceção é gerada e presa quando
a contagem é maior que cinco:

razor

@code {
private int currentCount = 0;

[CascadingParameter]
public Error? Error { get; set; }

private void IncrementCount()


{
try
{
currentCount++;

if (currentCount > 5)
{
throw new InvalidOperationException("Current count is
over five!");
}
}
catch (Exception ex)
{
Error?.ProcessError(ex);
}
}
}

Usando o componente anterior Error com as alterações anteriores feitas em um


Counter componente, o console de ferramentas para desenvolvedores do navegador

indica o erro interceptado e registrado:


Console

fail: BlazorSample.Shared.Error[0]
Error:ProcessError - Type: System.InvalidOperationException Message: Current
count is over five!

Se o ProcessError método participar diretamente da renderização, como mostrar uma


barra de mensagens de erro personalizada ou alterar os estilos CSS dos elementos
renderizados, chame StateHasChanged no final do ProcessErrors método para gerar
novamente a interface do usuário.

Como as abordagens nesta seção lidam com erros com uma try-catch instrução , a
conexão de SignalR um Blazor Server aplicativo entre o cliente e o servidor não é
interrompida quando ocorre um erro e o circuito permanece ativo. Outras exceções sem
tratamento permanecem fatais para um circuito. Para obter mais informações, consulte
a seção anterior sobre como um Blazor Server aplicativo reage a exceções sem
tratamento.

Erros de log com um provedor persistente


Se ocorrer uma exceção sem tratamento, a exceção será registrada ILogger em
instâncias configuradas no contêiner de serviço. Por padrão, Blazor os aplicativos fazem
logon na saída do console com o Provedor de Log do Console. Considere fazer logon
em um local no servidor (ou a API Web de back-end para Blazor WebAssembly
aplicativos) com um provedor que gerencia o tamanho do log e a rotação de log. Como
alternativa, o aplicativo pode usar um serviço de Gerenciamento de Desempenho de
Aplicativos (APM), como Aplicativo Azure Insights (Azure Monitor).

7 Observação

Os recursos nativos do Application Insights para dar suporte Blazor WebAssembly


a aplicativos e suporte a estrutura nativa Blazor para o Google Analytics podem
ficar disponíveis em versões futuras dessas tecnologias. Para obter mais
informações, consulte Support App Insights in Blazor WASM Client Side
(microsoft/ApplicationInsights-dotnet #2143) and Web analytics and
diagnostics (includes links to community implementations) (dotnet/aspnetcore
#5461) . Enquanto isso, um aplicativo do lado Blazor WebAssembly do cliente
pode usar o SDK do JavaScript do Application Insights com JS interoperabilidade
para registrar erros diretamente no Application Insights de um aplicativo do lado
do cliente.
Durante o desenvolvimento em um Blazor Server aplicativo, o aplicativo geralmente
envia todos os detalhes das exceções para o console do navegador para ajudar na
depuração. Na produção, erros detalhados não são enviados aos clientes, mas os
detalhes completos de uma exceção são registrados no servidor.

Você deve decidir quais incidentes registrar e o nível de gravidade dos incidentes
registrados. Usuários hostis podem ser capazes de disparar erros deliberadamente. Por
exemplo, não registre um incidente de um erro em que um desconhecido ProductId é
fornecido na URL de um componente que exibe detalhes do produto. Nem todos os
erros devem ser tratados como incidentes para registro em log.

Para obter mais informações, consulte os seguintes artigos:

Registro em log de Blazor no ASP.NET Core


Manipular erros no ASP.NET Core‡
Criar APIs Web com o ASP.NET Core

‡Aplica-se a Blazor Server aplicativos e outros aplicativos de ASP.NET Core do lado do


servidor que são aplicativos de back-end da API Web para Blazor. Blazor WebAssembly
os aplicativos podem capturar e enviar informações de erro no cliente para uma API
Web, que registra as informações de erro em um provedor de log persistente.

Locais em que podem ocorrer erros


A estrutura e o código do aplicativo podem disparar exceções sem tratamento em
qualquer um dos seguintes locais, que são descritos mais adiante nas seguintes seções
deste artigo:

Instanciação de componente
Métodos de ciclo de vida
Lógica de renderização
Manipuladores de eventos
Descarte de componentes
Interoperabilidade do JavaScript
Pré-geração

Instanciação de componente
Quando Blazor cria uma instância de um componente:

O construtor do componente é invocado.


Os construtores de serviços DI fornecidos ao construtor do componente por meio
da @inject diretiva ou do [Inject] atributo são invocados.

Um erro em um construtor executado ou um setter para qualquer [Inject]


propriedade resulta em uma exceção sem tratamento e impede que a estrutura
instancie o componente. Se o aplicativo for um Blazor Server aplicativo, o circuito
falhará. Se a lógica do construtor puder gerar exceções, o aplicativo deverá interceptar
as exceções usando uma try-catch instrução com tratamento de erros e registro em log.

Métodos de ciclo de vida


Durante o tempo de vida de um componente, Blazor invoca métodos de ciclo de vida.
Se qualquer método de ciclo de vida gerar uma exceção, de forma síncrona ou
assíncrona, a exceção será fatal para um Blazor Server circuito. Para que os
componentes lidem com erros em métodos de ciclo de vida, adicione a lógica de
tratamento de erros.

No exemplo a seguir, em que OnParametersSetAsync chama um método para obter um


produto:

Uma exceção gerada no ProductRepository.GetProductByIdAsync método é tratada


por uma try-catch instrução .
Quando o catch bloco é executado:
loadFailed é definido como true , que é usado para exibir uma mensagem de

erro para o usuário.


O erro é registrado.

razor

@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)


{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}

@code {
private ProductDetail? details;
private bool loadFailed;

[Parameter]
public int ProductId { get; set; }

protected override async Task OnParametersSetAsync()


{
try
{
loadFailed = false;
details = await
ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}",
ProductId);
}
}

public class ProductDetail


{
public string? ProductName { get; set; }
public string? Description { get; set; }
}

public interface IProductRepository


{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}

Lógica de renderização
A marcação declarativa em um Razor arquivo de componente ( .razor ) é compilada em
um método C# chamado BuildRenderTree. Quando um componente é renderizado,
BuildRenderTree executa e cria uma estrutura de dados que descreve os elementos, o
texto e os componentes filho do componente renderizado.

A lógica de renderização pode gerar uma exceção. Um exemplo desse cenário ocorre
quando @someObject.PropertyName é avaliado, mas @someObject é null . Para Blazor
Server aplicativos, uma exceção sem tratamento gerada pela lógica de renderização é
fatal para o circuito do aplicativo.
Para impedir um NullReferenceException na lógica de renderização, verifique se há um
null objeto antes de acessar seus membros. No exemplo a seguir, person.Address as
propriedades não serão acessadas se person.Address for null :

razor

@if (person.Address != null)


{
<div>@person.Address.Line1</div>
<div>@person.Address.Line2</div>
<div>@person.Address.City</div>
<div>@person.Address.Country</div>
}

O código anterior pressupõe que person não null é . Geralmente, a estrutura do código
garante que um objeto exista no momento em que o componente é renderizado.
Nesses casos, não é necessário verificar null na lógica de renderização. No exemplo
anterior, person pode ser garantido que exista porque person é criado quando o
componente é instanciado, como mostra o exemplo a seguir:

razor

@code {
private Person person = new();

...
}

Manipuladores de eventos
O código do lado do cliente dispara invocações de código C# quando manipuladores
de eventos são criados usando:

@onclick
@onchange

Outros @on... atributos


@bind

O código do manipulador de eventos pode gerar uma exceção sem tratamento nesses
cenários.

Se o aplicativo chamar o código que pode falhar por motivos externos, capture
exceções usando uma try-catch instrução com tratamento de erros e registro em log.
Se um manipulador de eventos gerar uma exceção sem tratamento (por exemplo, uma
consulta de banco de dados falhar) que não está presa e manipulada pelo código do
desenvolvedor:

A estrutura registra a exceção.


Em um Blazor Server aplicativo, a exceção é fatal para o circuito do aplicativo.

Descarte de componentes
Um componente pode ser removido da interface do usuário, por exemplo, porque o
usuário navegou até outra página. Quando um componente que implementa
System.IDisposable é removido da interface do usuário, a estrutura chama o método do
Dispose componente.

Se o método do Dispose componente gerar uma exceção sem tratamento em um


Blazor Server aplicativo, a exceção será fatal para o circuito do aplicativo.

Se a lógica de descarte puder gerar exceções, o aplicativo deverá interceptar as


exceções usando uma try-catch instrução com tratamento de erros e registro em log.

Para obter mais informações sobre o descarte de componentes, consulte ASP.NET Core
Razor ciclo de vida do componente.

Interoperabilidade do JavaScript
IJSRuntime é registrado pela Blazor estrutura. IJSRuntime.InvokeAsync permite que o
código .NET faça chamadas assíncronas para o runtime do JavaScript (JS) no navegador
do usuário.

As seguintes condições se aplicam ao tratamento de erros com InvokeAsync:

Se uma chamada para InvokeAsync falhar de forma síncrona, ocorrerá uma


exceção do .NET. Uma chamada para InvokeAsync pode falhar, por exemplo,
porque os argumentos fornecidos não podem ser serializados. O código do
desenvolvedor deve capturar a exceção. Se o código do aplicativo em um
manipulador de eventos ou método de ciclo de vida do componente não
manipular uma exceção em um Blazor Server aplicativo, a exceção resultante será
fatal para o circuito do aplicativo.
Se uma chamada para InvokeAsync falhar de forma assíncrona, o .NET Task falhará.
Uma chamada para InvokeAsync pode falhar, por exemplo, porque o JScódigo -
side gera uma exceção ou retorna uma Promise que foi concluída como rejected .
O código do desenvolvedor deve capturar a exceção. Se estiver usando o await
operador , considere encapsular a chamada de método em uma try-catch
instrução com tratamento de erros e registro em log. Caso contrário, em um Blazor
Server aplicativo, o código com falha resulta em uma exceção sem tratamento fatal
para o circuito do aplicativo.
Por padrão, as chamadas para InvokeAsync devem ser concluídas em um
determinado período ou então a chamada atinge o tempo limite. O período de
tempo limite padrão é de um minuto. O tempo limite protege o código contra
uma perda de conectividade de rede ou JS código que nunca envia uma
mensagem de conclusão. Se a chamada atingir o tempo limite, o resultado
System.Threading.Tasks falhará com um OperationCanceledException. Interceptar e
processar a exceção com registro em log.

Da mesma forma, JS o código pode iniciar chamadas para métodos .NET indicados pelo
[JSInvokable] atributo . Se esses métodos .NET gerarem uma exceção sem tratamento:

Em um Blazor Server aplicativo, a exceção não é tratada como fatal para o circuito
do aplicativo.
O JSlado Promise -é rejeitado.

Você tem a opção de usar o código de tratamento de erros no lado do .NET ou no JS


lado da chamada de método.

Para obter mais informações, consulte os seguintes artigos:

Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor


Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor

Pré-geração
Razor os componentes podem ser pré-gerados usando o Auxiliar de Marca de
Componente para que sua marcação HTML renderizada seja retornada como parte da
solicitação HTTP inicial do usuário.

No Blazor Server, a pré-geração funciona por:

Criando um novo circuito para todos os componentes pré-gerados que fazem


parte da mesma página.
Gerando o HTML inicial.
Tratando o circuito como disconnected até que o navegador do usuário estabeleça
uma SignalR conexão de volta para o mesmo servidor. Quando a conexão é
estabelecida, a interatividade no circuito é retomada e a marcação HTML dos
componentes é atualizada.
No pré-gerado Blazor WebAssembly, a pré-geração funciona por:

Gerando HTML inicial no servidor para todos os componentes pré-gerados que


fazem parte da mesma página.
Tornando o componente interativo no cliente depois que o navegador carregar o
código compilado do aplicativo e o runtime do .NET (se ainda não estiver
carregado) em segundo plano.

Se um componente gerar uma exceção sem tratamento durante a pré-geração, por


exemplo, durante um método de ciclo de vida ou na lógica de renderização:

Em Blazor aplicativos Sever, a exceção é fatal para o circuito. Em aplicativos pré-


gerados Blazor WebAssembly , a exceção impede a renderização do componente.
A exceção é gerada na pilha de chamadas do ComponentTagHelper.

Em circunstâncias normais, quando a pré-geração falha, continuar a compilar e


renderizar o componente não faz sentido porque um componente de trabalho não
pode ser renderizado.

Para tolerar erros que podem ocorrer durante a pré-geração, a lógica de tratamento de
erros deve ser colocada dentro de um componente que pode gerar exceções. Use try-
catch instruções com tratamento de erros e registro em log. Em vez de encapsular o
ComponentTagHelper em uma try-catch instrução , coloque a lógica de tratamento de
erro no componente renderizado pelo ComponentTagHelper.

Cenários avançados

Renderização recursiva
Os componentes podem ser aninhados recursivamente. Isso é útil para representar
estruturas de dados recursivas. Por exemplo, um TreeNode componente pode renderizar
mais TreeNode componentes para cada um dos filhos do nó.

Ao renderizar recursivamente, evite padrões de codificação que resultem em recursão


infinita:

Não renderize recursivamente uma estrutura de dados que contenha um ciclo. Por
exemplo, não renderize um nó de árvore cujos filhos incluem a si mesmo.
Não crie uma cadeia de layouts que contenha um ciclo. Por exemplo, não crie um
layout cujo layout seja ele mesmo.
Não permita que um usuário final viole invariáveis de recursão (regras) por meio
de entrada de dados mal-intencionados ou chamadas de interoperabilidade do
JavaScript.

Loops infinitos durante a renderização:

Faz com que o processo de renderização continue para sempre.


É equivalente a criar um loop nãoterminado.

Nesses cenários, o Blazor WebAssembly thread ou Blazor Server o circuito falha e


geralmente tenta:

Consuma o tempo de CPU permitido pelo sistema operacional indefinidamente.


Consumir uma quantidade ilimitada de memória. Consumir memória ilimitada é
equivalente ao cenário em que um loop não gerenciado adiciona entradas a uma
coleção em cada iteração.

Para evitar padrões de recursão infinita, verifique se o código de renderização recursivo


contém condições de parada adequadas.

Lógica de árvore de renderização personalizada


A maioria dos Razor componentes é implementada como Razor arquivos de
componente ( .razor ) e são compiladas pela estrutura para produzir lógica que opera
em um RenderTreeBuilder para renderizar sua saída. No entanto, um desenvolvedor
pode implementar RenderTreeBuilder manualmente a lógica usando código C# de
procedimento. Para obter mais informações, consulte ASP.NET Core Blazor cenários
avançados (construção de árvore de renderização).

2 Aviso

O uso da lógica do construtor de árvore de renderização manual é considerado um


cenário avançado e não seguro, não recomendado para o desenvolvimento geral
de componentes.

Se RenderTreeBuilder o código for escrito, o desenvolvedor deverá garantir a exatidão


do código. Por exemplo, o desenvolvedor deve garantir que:

As chamadas para OpenElement e CloseElement são balanceadas corretamente.


Os atributos são adicionados apenas nos locais corretos.

A lógica incorreta do construtor de árvore de renderização manual pode causar um


comportamento indefinido arbitrário, incluindo falhas, travamentos de aplicativo (Blazor
WebAssembly) ou servidor (Blazor Server) e vulnerabilidades de segurança.
Considere a lógica do construtor de árvore de renderização manual no mesmo nível de
complexidade e com o mesmo nível de perigo que escrever o código do assembly ou
instruções de MSIL (Microsoft Intermediate Language) manualmente.

Recursos adicionais
Registro em log de Blazor no ASP.NET Core
Tratar erros no ASP.NET Core†
Criar APIs Web com o ASP.NET Core
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)

†Aplica-se ao back-end ASP.NET Core aplicativos de API Web que os aplicativos do lado
Blazor WebAssembly do cliente usam para registro em log.
BlazorSignalR diretrizes de ASP.NET
Core
Artigo • 10/01/2023 • 53 minutos para o fim da leitura

Este artigo explica como configurar e gerenciar SignalR conexões em Blazor aplicativos.

Para obter diretrizes gerais sobre ASP.NET Core SignalR configuração, consulte os
tópicos na área Visão geral de ASP.NET Core SignalR da documentação. Para configurar
SignalR adicionado a um aplicativo hospedadoBlazor WebAssembly, por exemplo, no
tutorial Usar ASP.NET Core SignalR com Blazor ou um aplicativo autônomo Blazor
WebAssembly que usa SignalR, consulte ASP.NET Core SignalR configuração.

Desabilitar a compactação de resposta para


Recarga Dinâmica
Ao usar Recarga Dinâmica, desabilite o middleware de compactação de resposta no
Development ambiente. Os exemplos a seguir usam a verificação de ambiente existente

em um projeto criado a partir de um Blazor modelo de projeto. Se o código padrão de


um modelo de projeto é usado ou não, sempre chame UseResponseCompression
primeiro no pipeline de processamento de solicitação.

Em Program.cs um Blazor Server aplicativo:

C#

if (!app.Environment.IsDevelopment())
{
app.UseResponseCompression();
app.UseExceptionHandler("/Error");
app.UseHsts();
}

No Program.cs do Client projeto em uma solução hospedada Blazor WebAssembly :

C#

if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseResponseCompression();
app.UseExceptionHandler("/Error");
app.UseHsts();
}

SignalR negociação entre origens para


autenticação (Blazor WebAssembly)
Para configurar SignalRo cliente subjacente para enviar credenciais, como
cookiecabeçalhos de autenticação S ou HTTP:

Use SetBrowserRequestCredentials para definir Include em solicitações entre


origens fetch .

IncludeRequestCredentialsMessageHandler.cs :

C#

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;

public class IncludeRequestCredentialsMessageHandler :


DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken
cancellationToken)
{

request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include)
;
return base.SendAsync(request, cancellationToken);
}
}

Quando uma conexão de hub for criada, atribua a HttpMessageHandler à opção


HttpMessageHandlerFactory :

C#

private HubConnectionBuilder? hubConnection;

...

hubConnection = new HubConnectionBuilder()


.WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
{
options.HttpMessageHandlerFactory = innerHandler =>
new IncludeRequestCredentialsMessageHandler { InnerHandler
= innerHandler };
}).Build();

O exemplo anterior configura a URL de conexão do hub para o endereço URI


absoluto em /chathub , que é a URL usada no SignalR tutorial com Blazor no Index
componente ( Pages/Index.razor ). O URI também pode ser definido por meio de
uma cadeia de caracteres, por exemplo https://signalr.example.com , ou por meio
de configuração. Navigation é um injetado NavigationManager.

Para obter mais informações, consulte ASP.NET Core SignalR configuração.

Modo de renderização (Blazor WebAssembly)


Se um Blazor WebAssembly aplicativo que usa SignalR estiver configurado para pré-
gerar no servidor, a pré-geração ocorrerá antes que a conexão do cliente com o
servidor seja estabelecida. Para obter mais informações, consulte os seguintes artigos:

Auxiliar de marca de componente no ASP.NET Core


Pré-renderizar e integrar componentes Razor do ASP.NET Core

Recursos adicionais para Blazor WebAssembly


aplicativos
Hospedar e implantar o ASP.NET Core Blazor WebAssembly
Visão geral do ASP.NET Core SignalR
Configuração do ASP.NET Core SignalR
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)

Usar sessões autoadesivas para hospedagem


de webfarm (Blazor Server)
Um Blazor Server aplicativo remete em resposta à primeira solicitação de cliente, que
cria o estado da interface do usuário no servidor. Quando o cliente tenta criar uma
SignalR conexão, o cliente deve se reconectar ao mesmo servidor. Blazor Server os
aplicativos que usam mais de um servidor de back-end devem implementar sessões
autoadesivas para SignalR conexões.
7 Observação

O seguinte erro é gerado por um aplicativo que não habilitou sessões autoadesivas
em um webfarm:

blazor.server.js:1 Erro não iniciado (em promessa): invocação cancelada devido


ao fechamento da conexão subjacente.

Serviço do Azure SignalR (Blazor Server)


É recomendável usar o Serviço do Azure SignalR para Blazor Server aplicativos
hospedados no Microsoft Azure. O serviço funciona em conjunto com o Hub do Blazor
aplicativo para escalar verticalmente um Blazor Server aplicativo para um grande
número de conexões simultâneas SignalR . Além disso, o alcance global do Serviço e os
SignalR data centers de alto desempenho ajudam significativamente na redução da
latência devido à geografia.

As sessões autoadesivas são habilitadas para o Serviço do Azure SignalR definindo a


opção do serviço ou o valor de ServerStickyMode configuração como Required . Para
obter mais informações, consulte Hospedar e implantar ASP.NET Core Blazor Server.

Opções de manipulador de circuito para Blazor


Server aplicativos
Configure o Blazor Server circuito com o CircuitOptions mostrado na tabela a seguir.

Opção Padrão Descrição

DetailedErrors false Envie mensagens de exceção


detalhadas para JavaScript quando uma
exceção sem tratamento ocorrer no
circuito ou quando uma invocação de
método .NET por meio JS de
interoperabilidade resultar em uma
exceção.

DisconnectedCircuitMaxRetained 100 Número máximo de circuitos


desconectados que o servidor mantém
na memória por vez.
Opção Padrão Descrição

DisconnectedCircuitRetentionPeriod 3 Quantidade máxima de tempo em que


minutos um circuito desconectado é mantido na
memória antes de ser derrubado.

JSInteropDefaultCallTimeout 1 Quantidade máxima de tempo que o


minuto servidor aguarda antes de atingir o
tempo limite de uma invocação de
função JavaScript assíncrona.

MaxBufferedUnacknowledgedRenderBatches 10 Número máximo de lotes de


renderização não reconhecidos que o
servidor mantém na memória por
circuito em um determinado momento
para dar suporte à reconexão robusta.
Depois de atingir o limite, o servidor
para de produzir novos lotes de
renderização até que um ou mais lotes
sejam reconhecidos pelo cliente.

Configure as opções em Program.cs com um delegado de opções para


AddServerSideBlazor. O exemplo a seguir atribui os valores de opção padrão mostrados
na tabela anterior. Confirme se Program.cs usa o System namespace ( using System; ).

Em Program.cs :

C#

builder.Services.AddServerSideBlazor(options =>
{
options.DetailedErrors = false;
options.DisconnectedCircuitMaxRetained = 100;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
options.MaxBufferedUnacknowledgedRenderBatches = 10;
});

Para configurar o HubConnectionContext, use HubConnectionContextOptions com


AddHubOptions. Para obter descrições de opção, consulte ASP.NET Core SignalR
configuração. O exemplo a seguir atribui os valores de opção padrão. Confirme se
Program.cs usa o System namespace ( using System; ).

Em Program.cs :

C#
builder.Services.AddServerSideBlazor()
.AddHubOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
options.EnableDetailedErrors = false;
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.MaximumParallelInvocationsPerClient = 1;
options.MaximumReceiveMessageSize = 32 * 1024;
options.StreamBufferCapacity = 10;
});

2 Aviso

O valor padrão de MaximumReceiveMessageSize é 32 KB. Aumentar o valor pode


aumentar o risco de ataques de Negação de Serviço (DoS).

Para obter informações sobre Blazor Servero modelo de memória, consulte Hospedar e
implantar ASP.NET Core Blazor Server.

Blazor Configuração de rota do ponto de


extremidade do hub (Blazor Server)
No Program.cs , Blazor Server os aplicativos chamam MapBlazorHub para mapear o
BlazorHub para o caminho padrão do aplicativo. O Blazor Server script
( blazor.server.js ) aponta automaticamente para o ponto de extremidade criado por
MapBlazorHub.

Refletir o estado da conexão na interface do


usuário (Blazor Server)
Quando o cliente detecta que a conexão foi perdida, uma interface do usuário padrão é
exibida para o usuário enquanto o cliente tenta se reconectar. Se a reconexão falhar, o
usuário receberá a opção de tentar novamente.

Para personalizar a interface do usuário, defina um único elemento com um id de


components-reconnect-modal . O exemplo a seguir coloca o elemento na página do host.

Pages/_Host.cshtml :

CSHTML
<div id="components-reconnect-modal">
There was a problem with the connection!
</div>

7 Observação

Se mais de um elemento com um id de components-reconnect-modal for


renderizado pelo aplicativo, somente o primeiro elemento renderizado receberá
alterações de classe CSS para exibir ou ocultar o elemento.

Adicione os seguintes estilos CSS à folha de estilos do site.

wwwroot/css/site.css :

css

#components-reconnect-modal {
display: none;
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
display: block;
}

A tabela a seguir descreve as classes CSS aplicadas ao components-reconnect-modal


elemento pela Blazor estrutura.

Classe CSS Indica...

components- Uma conexão perdida. O cliente está tentando se reconectar. Mostre o modal.
reconnect-
show

components- Uma conexão ativa é restabelecida para o servidor. Ocultar o modal.


reconnect-
hide

components- Falha na reconexão, provavelmente devido a uma falha de rede. Para tentar a
reconnect- reconexão, chame window.Blazor.reconnect() em JavaScript.
failed
Classe CSS Indica...

components- Reconexão rejeitada. O servidor foi alcançado, mas recusou a conexão e o estado
reconnect- do usuário no servidor foi perdido. Para recarregar o aplicativo, chame
rejected location.reload() em JavaScript. Esse estado de conexão pode resultar quando:

Ocorre uma falha no circuito do lado do servidor.


O cliente é desconectado tempo suficiente para que o servidor solte o
estado do usuário. As instâncias dos componentes do usuário são
descartadas.
O servidor é reiniciado ou o processo de trabalho do aplicativo é reciclado.

Personalize o atraso antes que a exibição de reconexão apareça definindo a transition-


delay propriedade no CSS do site para o elemento modal. O exemplo a seguir define o
atraso de transição de 500 ms (padrão) para 1.000 ms (1 segundo).

wwwroot/css/site.css :

css

#components-reconnect-modal {
transition: visibility 0s linear 1000ms;
}

Para exibir a tentativa de reconexão atual, defina um elemento com um id de


components-reconnect-current-attempt . Para exibir o número máximo de repetições de

reconexão, defina um elemento com um id de components-reconnect-max-retries . O


exemplo a seguir coloca esses elementos dentro de um elemento modal de tentativa de
reconexão na página do host seguindo o exemplo anterior.

Pages/_Host.cshtml :

CSHTML

<div id="components-reconnect-modal">
There was a problem with the connection!
(Current reconnect attempt:
<span id="components-reconnect-current-attempt"></span> /
<span id="components-reconnect-max-retries"></span>)
</div>

Quando o modal de reconexão personalizado é exibido, ele renderiza conteúdo


semelhante ao seguinte com base no código anterior:

HTML
There was a problem with the connection! (Current reconnect attempt: 3 / 8)

Modo de renderização (Blazor Server)


Por padrão, Blazor Server os aplicativos pré-geram a interface do usuário no servidor
antes que a conexão do cliente com o servidor seja estabelecida. Para obter mais
informações, consulte Auxiliar de marca de componente no ASP.NET Core.

Blazor Inicialização
Configure o início manual do circuito de um Blazor aplicativo no Pages/_Host.cshtml
arquivo (Blazor Server) ou wwwroot/index.html (hospedado Blazor WebAssembly com
SignalRSignalR implementado):

Adicione um autostart="false" atributo à <script> marca para o blazor.


{server|webassembly}.js script.

Coloque um script que chame Blazor.start() depois que o Blazor script for
carregado e dentro da marca de fechamento </body> .

Quando autostart está desabilitado, qualquer aspecto do aplicativo que não dependa
do circuito funciona normalmente. Por exemplo, o roteamento do lado do cliente está
operacional. No entanto, qualquer aspecto que dependa do circuito não estará
operacional até Blazor.start() que seja chamado. O comportamento do aplicativo é
imprevisível sem um circuito estabelecido. Por exemplo, os métodos de componente
não são executados enquanto o circuito está desconectado.

Para obter mais informações, incluindo como inicializar quando o documento estiver
pronto e como encadear para um JS Promise , consulte ASP.NET Core Blazor
inicializaçãoBlazor.

Configurar SignalR tempos limite e Keep-Alive


no cliente
Configure os seguintes valores para o cliente:

serverTimeoutInMilliseconds : o tempo limite do servidor em milissegundos. Se

esse tempo limite passar sem receber nenhuma mensagem do servidor, a conexão
será encerrada com um erro. O valor do tempo limite padrão é de 30 segundos. O
tempo limite do servidor deve ser pelo menos o dobro do valor atribuído ao
intervalo de Keep-Alive ( keepAliveIntervalInMilliseconds ).
keepAliveIntervalInMilliseconds : intervalo padrão no qual executar ping no

servidor. Essa configuração permite que o servidor detecte desconexões rígidas,


como quando um cliente desconecta o computador da rede. O ping ocorre no
máximo com a frequência que o servidor pinga. Se o servidor executar pings a
cada cinco segundos, atribuir um valor menor que 5000 (5 segundos) executará
pings a cada cinco segundos. O valor padrão é 15 segundos. O intervalo de Keep-
Alive deve ser menor ou igual a metade do valor atribuído ao tempo limite do
servidor ( serverTimeoutInMilliseconds ).

O exemplo a seguir Pages/_Host.cshtml para (Blazor Server) ou wwwroot/index.html


(Blazor WebAssembly) usa valores padrão:

HTML

<script src="_framework/blazor.{HOSTING MODEL}.js" autostart="false">


</script>
<script>
Blazor.start({
configureSignalR: function (builder) {
let c = builder.build();
c.serverTimeoutInMilliseconds = 30000;
c.keepAliveIntervalInMilliseconds = 15000;
builder.build = () => {
return c;
};
}
});
</script>

Na marcação anterior, o {HOSTING MODEL} espaço reservado é server para um Blazor


Server aplicativo ou webassembly para um Blazor WebAssembly aplicativo.

Ao criar uma conexão de hub em um componente, defina o ServerTimeout (padrão: 30


segundos), HandshakeTimeout (padrão: 15 segundos) e KeepAliveInterval (padrão: 15
segundos) no compilado HubConnection. O exemplo a Index seguir, com base no
componente no SignalR tutorial comBlazor, usa valores padrão:

C#

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

hubConnection.On<string, string>("ReceiveMessage", (user, message) =>


...

await hubConnection.StartAsync();
}

Ao alterar os valores do tempo limite do servidor (ServerTimeout) ou do intervalo de


Keep-Alive (KeepAliveInterval:

O tempo limite do servidor deve ser pelo menos o dobro do valor atribuído ao
intervalo de Keep-Alive.
O intervalo de Keep-Alive deve ser menor ou igual a metade do valor atribuído ao
tempo limite do servidor.

Para obter mais informações, consulte as seções Falhas globais de implantação e


conexão dos seguintes artigos:

Hospedar e implantar o ASP.NET Core Blazor Server


Hospedar e implantar o ASP.NET Core Blazor WebAssembly

Modificar o manipulador de reconexão (Blazor


Server)
Os eventos de conexão de circuito do manipulador de reconexão podem ser
modificados para comportamentos personalizados, como:

Para notificar o usuário se a conexão for descartada.


Para executar o log (do cliente) quando um circuito estiver conectado.

Para modificar os eventos de conexão, registre retornos de chamada para as seguintes


alterações de conexão:

As conexões descartadas usam onConnectionDown .


As conexões estabelecidas/restabelecidas usam onConnectionUp .

onConnectionUp E onConnectionDown devem ser especificados.

Pages/_Host.cshtml :

CSHTML
<body>
...

<script src="_framework/blazor.server.js" autostart="false"></script>


<script>
Blazor.start({
reconnectionHandler: {
onConnectionDown: (options, error) => console.error(error),
onConnectionUp: () => console.log("Up, up, and away!")
}
});
</script>
</body>

Atualize automaticamente a página quando a reconexão


falhar (Blazor Server)
O comportamento de reconexão padrão exige que o usuário execute uma ação manual
para atualizar a página após a falha na reconexão. No entanto, um manipulador de
reconexão personalizado pode ser usado para atualizar automaticamente a página:

Pages/_Host.cshtml :

CSHTML

<body>
...

<div id="reconnect-modal" style="display: none;"></div>


<script src="_framework/blazor.server.js" autostart="false"></script>
<script src="boot.js"></script>
</body>

wwwroot/boot.js :

JavaScript

(() => {
const maximumRetryCount = 3;
const retryIntervalMilliseconds = 5000;
const reconnectModal = document.getElementById('reconnect-modal');

const startReconnectionProcess = () => {


reconnectModal.style.display = 'block';

let isCanceled = false;

(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of
${maximumRetryCount}`;

await new Promise(resolve => setTimeout(resolve,


retryIntervalMilliseconds));

if (isCanceled) {
return;
}

try {
const result = await Blazor.reconnect();
if (!result) {
// The server was reached, but the connection was rejected;
reload the page.
location.reload();
return;
}

// Successfully reconnected to the server.


return;
} catch {
// Didn't reach the server; try again.
}
}

// Retried too many times; reload the page.


location.reload();
})();

return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};

let currentReconnectionProcess = null;

Blazor.start({
reconnectionHandler: {
onConnectionDown: () => currentReconnectionProcess ??=
startReconnectionProcess(),
onConnectionUp: () => {
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
},
},
});
})();
Para obter mais informações sobre a inicialização de Blazor, veja Inicialização de Blazor
no ASP.NET Core.

Ajustar a contagem e o intervalo de repetição


de reconexão (Blazor Server)
Para ajustar a contagem e o intervalo de repetição de reconexão, defina o número de
repetições ( maxRetries ) e o período em milissegundos permitidos para cada tentativa
de repetição ( retryIntervalMilliseconds ).

Pages/_Host.cshtml :

CSHTML

<body>
...

<script src="_framework/blazor.server.js" autostart="false"></script>


<script>
Blazor.start({
reconnectionOptions: {
maxRetries: 3,
retryIntervalMilliseconds: 2000
}
});
</script>
</body>

Para obter mais informações sobre a inicialização de Blazor, veja Inicialização de Blazor
no ASP.NET Core.

Desconectar o Blazor circuito do cliente (Blazor


Server)
Por padrão, um Blazor circuito é desconectado quando o evento de unload página é
disparado. Para desconectar o circuito para outros cenários no cliente, invoque
Blazor.disconnect no manipulador de eventos apropriado. No exemplo a seguir, o
circuito é desconectado quando a página está oculta (pagehide evento ):

JavaScript

window.addEventListener('pagehide', () => {
Blazor.disconnect();
});
Para obter mais informações sobre a inicialização de Blazor, veja Inicialização de Blazor
no ASP.NET Core.

Blazor Server manipulador de circuito


Blazor Server permite que o código defina um manipulador de circuito, o que permite a
execução de código em alterações no estado do circuito de um usuário. Um
manipulador de circuito é implementado derivando de CircuitHandler e registrando a
classe no contêiner de serviço do aplicativo. O exemplo a seguir de um manipulador de
circuito rastreia conexões abertas SignalR .

TrackingCircuitHandler.cs :

C#

using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler


{
private HashSet<Circuit> circuits = new();

public override Task OnConnectionUpAsync(Circuit circuit,


CancellationToken cancellationToken)
{
circuits.Add(circuit);

return Task.CompletedTask;
}

public override Task OnConnectionDownAsync(Circuit circuit,


CancellationToken cancellationToken)
{
circuits.Remove(circuit);

return Task.CompletedTask;
}

public int ConnectedCircuits => circuits.Count;


}

Manipuladores de circuito são registrados usando DI. As instâncias com escopo são
criadas por instância de um circuito. Usando o TrackingCircuitHandler no exemplo
anterior, um serviço singleton é criado porque o estado de todos os circuitos deve ser
rastreado.

Em Program.cs :
C#

builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Se os métodos de um manipulador de circuito personalizado gerarem uma exceção sem


tratamento, a exceção será fatal para o Blazor Server circuito. Para tolerar exceções no
código de um manipulador ou métodos chamados, encapsule o código em uma ou
mais try-catch instruções com tratamento de erros e registro em log.

Quando um circuito termina porque um usuário se desconectou e a estrutura está


limpando o estado do circuito, a estrutura descarta o escopo di do circuito. Descartar o
escopo descarta todos os serviços DI com escopo de circuito que implementam
System.IDisposable. Se qualquer serviço de DI gerar uma exceção sem tratamento
durante o descarte, a estrutura registrará a exceção em log. Para saber mais, confira
Injeção de dependência do Blazor no ASP.NET Core.

Evitar IHttpContextAccessor em Razor


componentes
Não use IHttpContextAccessor em Razor componentes de Blazor Server aplicativos.
Blazoros aplicativos são executados fora do contexto do pipeline de ASP.NET Core. Não
HttpContext há garantia de que o IHttpContextAccessoresteja disponível no e
HttpContext não tem garantia de manter o contexto que iniciou o Blazor aplicativo. Para
obter mais informações, consulte Implicações de segurança do uso
IHttpContextAccessor no Blazor Server (dotnet/aspnetcore #45699) . Para obter mais
informações sobre como manter o estado do usuário em Blazor Server aplicativos,
consulte ASP.NET Core Blazor gerenciamento de estado.

Recursos adicionais para Blazor Server


aplicativos
Hospedar e implantar o ASP.NET Core Blazor Server
Visão geral do ASP.NET Core SignalR
Configuração do ASP.NET Core SignalR
Diretrizes de mitigação de ameaças para ASP.NET Core Blazor Server
Blazor Server eventos de reconexão e eventos do ciclo de vida do componente
O que é o Serviço do Azure SignalR ?
Guia de desempenho do Serviço do Azure SignalR
Publicar um aplicativo de ASP.NET Core SignalR para Serviço de Aplicativo do
Azure
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Blazor ASP.NET Core arquivos estáticos
Artigo • 28/11/2022 • 9 minutos para o fim da leitura

Este artigo descreve a configuração para servir arquivos estáticos em Blazor aplicativos.

Para obter mais informações sobre soluções em seções que se aplicam a aplicativos
hospedadosBlazor WebAssembly, consulte Ferramentas para ASP.NET CoreBlazor.

Middleware de arquivos estáticos


Esta seção se aplica aos Blazor Server aplicativos e ao Server aplicativo de uma solução
hospedada Blazor WebAssembly .

Configure o Middleware de Arquivo Estático para atender ativos estáticos aos clientes
chamando UseStaticFiles o pipeline de processamento de solicitação do aplicativo. Saiba
mais em Arquivos estáticos no ASP.NET Core.

Arquivos estáticos em ambientes não


estáticos Development para Blazor Server
aplicativos
Esta seção se aplica a Blazor Server aplicativos.

Em Blazor Server aplicativos executados localmente, os ativos web estáticos só são


habilitados por padrão no Development ambiente. Para habilitar arquivos estáticos para
ambientes diferentes Development de durante o desenvolvimento local e testes (por
exemplo, Staging), chame UseStaticWebAssets o WebApplicationBuilder in Program.cs .

2 Aviso

Chame UseStaticWebAssets o ambiente exato para impedir a ativação do recurso


na produção, pois ele serve arquivos de locais separados em disco diferente do
projeto , se chamado em um ambiente de produção. O exemplo nesta seção verifica
o Staging ambiente chamando IsStaging.

C#

if (builder.Environment.IsStaging())
{
builder.WebHost.UseStaticWebAssets();
}

Caminho de base de ativos da Web estático


Esta seção se aplica a aplicativos autônomos Blazor WebAssembly e soluções hospedadas
Blazor WebAssembly .

Por padrão, a publicação de um Blazor WebAssembly aplicativo coloca os ativos


estáticos do aplicativo, incluindo Blazor arquivos de estrutura ( _framework ativos de
pasta), no caminho raiz ( / ) na saída publicada. A <StaticWebAssetBasePath>
propriedade especificada no arquivo de projeto ( .csproj ) define o caminho base como
um caminho não raiz:

XML

<PropertyGroup>
<StaticWebAssetBasePath>{PATH}</StaticWebAssetBasePath>
</PropertyGroup>

No exemplo anterior, o {PATH} espaço reservado é o caminho.

A <StaticWebAssetBasePath> propriedade é mais comumente usada para controlar os


caminhos para ativos estáticos publicados de vários Blazor WebAssembly aplicativos em
uma única implantação hospedada. Para obter mais informações, consulte Vários
aplicativos de ASP.NET Core Blazor WebAssembly hospedados. A propriedade também
é eficaz em aplicativos autônomos Blazor WebAssembly .

Sem definir a <StaticWebAssetBasePath> propriedade, o aplicativo cliente de uma


solução hospedada ou um aplicativo autônomo é publicado nos seguintes caminhos:

Server No projeto de uma solução hospedadaBlazor


WebAssembly: /BlazorHostedSample/Server/bin/Release/{TFM}/publish/wwwroot/
Em um aplicativo autônomo Blazor WebAssembly :
/BlazorStandaloneSample/bin/Release/{TFM}/publish/wwwroot/

Nos exemplos anteriores, o {TFM} espaço reservado é o TFM (Moniker da Estrutura de


Destino) (por exemplo, net6.0 ).

Se a <StaticWebAssetBasePath> propriedade no Client projeto de um aplicativo


hospedado Blazor WebAssembly ou em um aplicativo autônomo Blazor WebAssembly
definir o caminho do ativo estático publicado como app1 , o caminho raiz para o
aplicativo na saída publicada será /app1 .

Client No arquivo de projeto do aplicativo ( .csproj ) ou no arquivo de projeto do


aplicativo autônomo Blazor WebAssembly ( .csproj ):

XML

<PropertyGroup>
<StaticWebAssetBasePath>app1</StaticWebAssetBasePath>
</PropertyGroup>

Na saída publicada:

Caminho para o aplicativo cliente no Server projeto de uma solução hospedada


Blazor WebAssembly :
/BlazorHostedSample/Server/bin/Release/{TFM}/publish/wwwroot/app1/

Caminho para um aplicativo autônomo Blazor WebAssembly :


/BlazorStandaloneSample/bin/Release/{TFM}/publish/wwwroot/app1/

Nos exemplos anteriores, o {TFM} espaço reservado é o TFM (Moniker da Estrutura de


Destino) (por exemplo, net6.0 ).

Blazor Server mapeamentos de arquivo e


opções de arquivo estático
Para criar mapeamentos de arquivos adicionais com um
FileExtensionContentTypeProvider ou configurar outro StaticFileOptions, use uma das
abordagens a seguir. Nos exemplos a seguir, o {EXTENSION} espaço reservado é a
extensão de arquivo e o {CONTENT TYPE} espaço reservado é o tipo de conteúdo.

Configurar opções por meio da DI (injeção de dependência) ao Program.cs usar


StaticFileOptions:

C#

using Microsoft.AspNetCore.StaticFiles;

...

var provider = new FileExtensionContentTypeProvider();


provider.Mappings["{EXTENSION}"] = "{CONTENT TYPE}";

builder.Services.Configure<StaticFileOptions>(options =>
{
options.ContentTypeProvider = provider;
});

Como essa abordagem configura o mesmo provedor de arquivos usado para servir
blazor.server.js , verifique se sua configuração personalizada não interfere no
serviço blazor.server.js . Por exemplo, não remova o mapeamento para arquivos
JavaScript configurando o provedor com provider.Mappings.Remove(".js") .

Use duas chamadas paraUseStaticFiles: Program.cs


Configure o provedor de arquivos personalizado na primeira chamada com
StaticFileOptions.
O segundo middleware serve blazor.server.js , que usa a configuração de
arquivos estáticos padrão fornecida pela Blazor estrutura.

C#

using Microsoft.AspNetCore.StaticFiles;

...

var provider = new FileExtensionContentTypeProvider();


provider.Mappings["{EXTENSION}"] = "{CONTENT TYPE}";

app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider =


provider });
app.UseStaticFiles();

Você pode evitar interferir no serviço _framework/blazor.server.js usando


MapWhen para executar um middleware de arquivo estático personalizado:

C#

app.MapWhen(ctx => !ctx.Request.Path


.StartsWithSegments("/_framework/blazor.server.js"),
subApp => subApp.UseStaticFiles(new StaticFileOptions() { ...
}));

Recursos adicionais
Caminho base do aplicativo
Vários aplicativos de ASP.NET Core Blazor WebAssembly hospedados
Componentes Razor no ASP.NET Core
Artigo • 26/09/2022 • 137 minutos para o fim da leitura

Este artigo explica como criar e usar componentes Razor em aplicativos Blazor, incluindo
diretrizes sobre a sintaxe Razor, a nomenclatura de componentes, namespaces e
parâmetros de componente.

Aplicativos Blazor são criados usando componentes Razor, conhecidos informalmente


como componentes Blazor. Um componente é uma parte independente da interface do
usuário com lógica de processamento para habilitar comportamento dinâmico. Os
componentes podem ser aninhados, reutilizados, compartilhados entre projetos e
usados em aplicativos MVC e Razor Pages.

Classes de componentes
Os componentes são implementados usando uma combinação de marcação C# e HTML
em arquivos de componente Razor com a extensão de arquivo .razor .

Sintaxe de Razor
Os componentes usam a sintaxe Razor. Dois recursos Razor são amplamente usados
pelos componentes: diretivas e atributos de diretiva. Trata-se de palavras-chave
reservadas prefixadas com @ que aparecem na marcação Razor:

Diretivas: alteram a forma como a marcação do componente é analisada ou


funciona. Por exemplo, a diretiva @page especifica um componente roteável com
um modelo de rota e pode ser acessada diretamente pela solicitação de um
usuário no navegador em uma URL específica.
Atributos de diretiva: alteram a forma como um elemento de componente é
analisado ou funciona. Por exemplo, o atributo de diretiva @bind de um elemento
<input> associa dados ao valor do elemento.

As diretivas e os atributos de diretiva usados nos componentes são explicados mais


adiante neste artigo e em outros artigos do conjunto de documentação Blazor. Para
obter informações gerais sobre a sintaxe Razor, confira a Referência da sintaxe Razor
para ASP.NET Core.

Nomes
O nome de um componente precisa começar com um caractere maiúsculo:
ProductDetail.razor é válido.

productDetail.razor é inválido.

Convenções de nomenclatura Blazor comuns usadas em toda a documentação de


Blazor incluem:

Os caminhos de arquivo do componente usam PascalCase† e aparecem antes de


mostrar exemplos de código de componentes. Os caminhos indicam locais de
pasta típicos. Por exemplo, Pages/ProductDetail.razor indica que o componente
ProductDetail tem um nome de arquivo ProductDetail.razor e reside na pasta

Pages do aplicativo.
Os caminhos de arquivo de componente dos componentes roteáveis fazem a
correspondência das URLs usando hifens no lugar dos espaços entre as palavras
no modelo de rota de um componente. Por exemplo, um componente
ProductDetail com um modelo de rota /product-detail ( @page "/product-

detail" ) é solicitado em um navegador na URL relativa /product-detail .

PascalCase† (camelCase iniciada por maiúscula) é uma convenção de nomenclatura sem


espaços e pontuação e com a primeira letra de cada palavra maiúscula, incluindo a
primeira palavra.

Roteamento
O roteamento em Blazor é realizado fornecendo um modelo de rota para cada
componente acessível no aplicativo com uma diretiva @page. Quando um arquivo
Razor com uma diretiva @page é compilado, a classe gerada recebe um RouteAttribute
especificando o modelo da rota. No runtime, o roteador pesquisa por classes de
componente com um RouteAttribute e renderiza qualquer componente que tenha um
modelo de rota correspondente à URL solicitada.

O componente HelloWorld a seguir usa um modelo de rota /hello-world . A página da


Web renderizada do componente é acessada na URL relativa /hello-world . Ao executar
um aplicativo Blazor localmente com o protocolo, o host e a porta padrão, o
componente HelloWorld é solicitado no navegador em https://localhost:5001/hello-
world . Os componentes que produzem páginas da Web geralmente residem na pasta

Pages , mas você pode usar qualquer pasta para armazenar componentes, inclusive
dentro de pastas aninhadas.

Pages/HelloWorld.razor :

razor
@page "/hello-world"

<h1>Hello World!</h1>

O componente anterior é carregado no navegador /hello-world , independentemente


de você adicioná-lo à navegação da interface do usuário do aplicativo. Opcionalmente,
os componentes podem ser adicionados ao componente NavMenu para que um link para
o componente apareça na navegação baseada em interface do usuário do aplicativo.

Para o componente HelloWorld anterior, você pode adicionar um componente NavLink


ao componente NavMenu na pasta Shared . Para obter mais informações, incluindo
descrições dos componentes NavLink e NavMenu , consulte Roteamento e navegação de
Blazor no ASP.NET Core.

Marcação
A interface do usuário de um componente é definida usando a sintaxe Razor, composta
por marcação Razor, C# e HTML. Quando um aplicativo é compilado, a marcação HTML
e a lógica de renderização de C# são convertidas em uma classe de componente. O
nome da classe gerada corresponde ao nome do arquivo.

Os membros da classe de componente são definidos em um ou mais blocos @code.


Nos blocos @code, o estado do componente é especificado e processado com C#:

Inicializadores de propriedade e de campo.


Valores de parâmetro de argumentos passados por parâmetros de rota e
componentes pai.
Métodos para tratamento de eventos do usuário, eventos de ciclo de vida e lógica
de componente personalizada.

Os membros do componente são usados na lógica de renderização usando expressões


em C# que começam com o símbolo @ . Por exemplo, um campo C# é renderizado
adicionando o prefixo @ ao nome do campo. O seguinte componente Markup avalia e
renderiza:

headingFontStyle para o valor font-style da propriedade CSS do elemento de

título.
headingText para o conteúdo do elemento de título.

Pages/Markup.razor :

razor
@page "/markup"

<h1 style="font-style:@headingFontStyle">@headingText</h1>

@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}

7 Observação

Exemplos ao longo da documentação de Blazor especificam o modificador de


acesso private para membros privados. Membros privados têm como escopo a
classe de um componente. No entanto, C# pressupõe o modificador de acesso
private quando nenhum modificador de acesso está presente, portanto, marcar

explicitamente os membros " private " em seu código é opcional. Para obter mais
informações sobre os modificadores de acesso, consulte Modificadores de acesso
(Guia de programação em C#).

A estrutura Blazor processa um componente internamente como uma árvore de


renderização , que é a combinação do DOM (Modelo de Objeto do Documento) e
do CSSOM (Modelo de Objeto de Folhas de Estilos em Cascata) . Após o componente
ser renderizado inicialmente, a árvore de renderização dele é regenerada em resposta a
eventos. O Blazor compara a nova árvore de renderização com a anterior e aplica as
modificações ao DOM do navegador para exibição. Para saber mais, consulte
Renderização de componentes Razor no ASP.NET Core.

Os componentes são classes C# comuns e podem ser colocados em qualquer lugar de


um projeto. Componentes que produzem páginas da Web geralmente residem na pasta
Pages . Frequentemente, componentes que não são de página são colocados na pasta
Shared ou em uma pasta personalizada adicionada ao projeto.

A sintaxe Razor para estruturas de controle, diretivas e atributos de diretiva em C# é em


minúsculas (por exemplo, @if, @code, @bind). Os nomes de propriedade são em
maiúsculas (por exemplo, @Body para LayoutComponentBase.Body).

Métodos assíncronos ( async ) não dão suporte ao retorno


de void
A estrutura Blazor não acompanha métodos assíncronos com retorno de void ( async ).
Como resultado, exceções não serão capturadas se void for retornado. Sempre retorne
um Task de métodos assíncronos.

Componentes aninhados
Os componentes podem incluir outros componentes declarando-os usando a sintaxe
HTML. A marcação para uso de um componente é semelhante a uma marca HTML, em
que o nome da marca é o tipo de componente.

Considere o componente Heading a seguir, que pode ser usado por outros
componentes para exibir um título.

Shared/Heading.razor :

razor

<h1 style="font-style:@headingFontStyle">Heading Example</h1>

@code {
private string headingFontStyle = "italic";
}

A marcação a seguir no componente HeadingExample renderiza o componente Heading


anterior no local em que a marca <Heading /> é exibida.

Pages/HeadingExample.razor :

razor

@page "/heading-example"

<Heading />

Se um componente contiver um elemento HTML com uma primeira letra maiúscula que
não corresponde a um nome de componente no mesmo namespace, será emitido um
aviso indicando que o elemento tem um nome inesperado. Adicionar um diretiva
@using para o namespace do componente disponibiliza o componente, o que resolve o
aviso. Para obter mais informações, confira a seção Namespaces.

O componente de exemplo Heading mostrado nesta seção não tem uma diretiva
@page, portanto o componente Heading não é diretamente acessível a um usuário por
meio de uma solicitação direta no navegador. No entanto, qualquer componente com
uma diretiva @page pode ser aninhado em outro componente. Se o componente
Heading estivesse diretamente acessível, com a inclusão de @page "/heading" na parte
superior do arquivo Razor, ele seria renderizado para solicitações de navegador em
/heading e /heading-example .

Namespaces
Normalmente, o namespace de um componente é derivado do namespace raiz do
aplicativo e da localização do componente (pasta) no aplicativo. Se o namespace raiz do
aplicativo for BlazorSample e o componente Counter residir na pasta Pages :

O namespace do componente Counter será BlazorSample.Pages .


O nome do tipo totalmente qualificado do componente será
BlazorSample.Pages.Counter .

Para pastas personalizadas que contêm componentes, adicione uma diretiva @using ao
componente pai ou ao arquivo _Imports.razor do aplicativo. O seguinte exemplo
disponibiliza componentes na pasta Components :

razor

@using BlazorSample.Components

7 Observação

As diretivas @using no arquivo _Imports.razor são aplicadas somente a arquivos


Razor ( .razor ), não a arquivos C# ( .cs ).

Os componentes também podem ser referenciados usando os respectivos nomes


totalmente qualificados, o que não requer uma diretiva @using. O seguinte exemplo faz
referência direta ao componente ProductDetail na pasta Components do aplicativo:

razor

<BlazorSample.Components.ProductDetail />

O namespace de um componente criado com Razor é baseado no seguinte (em ordem


de prioridade):

A diretiva @namespace na marcação do arquivo Razor (por exemplo, @namespace


BlazorSample.CustomNamespace ).
O projeto RootNamespace está no arquivo de projeto (por exemplo,
<RootNamespace>BlazorSample</RootNamespace> ).
O nome do projeto, extraído do nome do arquivo de projeto ( .csproj ), e o
caminho da raiz do projeto para o componente. Por exemplo, a estrutura resolve
{PROJECT ROOT}/Pages/Index.razor com um namespace de projeto BlazorSample

( BlazorSample.csproj ) para o namespace BlazorSample.Pages do componente


Index . {PROJECT ROOT} é o caminho da raiz do projeto. Os componentes seguem

as regras de associação de nome em C#. Para o componente Index neste


exemplo, os componentes no escopo são todos:
Na mesma pasta, Pages .
Os componentes na raiz do projeto que não especificam explicitamente um
namespace diferente.

Não há suporte para o seguinte:

A qualificação global::.
Importação de componentes com instruções using com alias. Por exemplo, @using
Foo = Bar não é compatível.
Nomes parcialmente qualificados. Por exemplo, você não pode adicionar @using
BlazorSample a um componente e, depois, referenciar o componente NavMenu na

pasta do Shared aplicativo ( Shared/NavMenu.razor ) com <Shared.NavMenu>


</Shared.NavMenu> .

Suporte para classe parcial


Os componentes são gerados como classes parciais de C# e são criados usando uma
das seguintes abordagens:

Um arquivo contém o código C# definido em um ou mais blocos @code,


marcação HTML e marcação Razor. Os modelos de projeto Blazor definem os
respectivos componentes usando essa abordagem de arquivo único.
O HTML e a marcação Razor são colocados em um arquivo Razor ( .razor ). O
código C# é colocado em um arquivo code-behind definido como uma classe
parcial ( .cs ).

7 Observação

A folha de estilos de um componente que define estilos específicos do


componente é um arquivo separado ( .css ). O isolamento de CSS Blazor é descrito
posteriormente em Isolamento de CSS de Blazor no ASP.NET Core.
O exemplo a seguir mostra o componente padrão Counter com um bloco @code em
um aplicativo gerado com base em um modelo de projeto Blazor. A marcação e o
código C# ficam no mesmo arquivo. Essa é a abordagem mais comum adotada na
criação de componentes.

Pages/Counter.razor :

razor

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

O seguinte componente Counter divide o HTML e a marcação Razor do código C#


usando um arquivo code-behind com uma classe parcial:

Pages/CounterPartialClass.razor :

razor

@page "/counter-partial-class"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

Pages/CounterPartialClass.razor.cs :

C#
namespace BlazorSample.Pages
{
public partial class CounterPartialClass
{
private int currentCount = 0;

void IncrementCount()
{
currentCount++;
}
}
}

As diretivas @using no arquivo _Imports.razor são aplicadas somente a arquivos Razor


( .razor ), não a arquivos C# ( .cs ). Adicione os namespaces a um arquivo de classe
parcial conforme necessário.

Namespaces típicos usados pelos componentes:

C#

using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;

Namespaces típicos também incluem o namespace do aplicativo e o namespace


correspondente à pasta Shared do aplicativo:

C#

using BlazorSample;
using BlazorSample.Shared;

Especificar uma classe base


A diretiva @inherits é usada para especificar uma classe base para um componente. O
exemplo a seguir mostra como um componente pode herdar uma classe base para
fornecer suas propriedades e métodos. A classe base BlazorRocksBase é derivada de
ComponentBase.

Pages/BlazorRocks.razor :
razor

@page "/blazor-rocks"
@inherits BlazorRocksBase

<h1>@BlazorRocksText</h1>

BlazorRocksBase.cs :

C#

using Microsoft.AspNetCore.Components;

namespace BlazorSample
{
public class BlazorRocksBase : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
}

Parâmetros do componente
Os parâmetros de componente passam dados para os componentes e são definidos
usando propriedades C# públicas na classe de componente com o atributo [Parameter].
No exemplo a seguir, um tipo de referência interno (System.String) e um tipo de
referência definido pelo usuário ( PanelBody ) são passados como parâmetros de
componente.

PanelBody.cs :

C#

public class PanelBody


{
public string? Text { get; set; }
public string? Style { get; set; }
}

Shared/ParameterChild.razor :

razor

<div class="card w-25" style="margin-bottom:15px">


<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
</div>
</div>

@code {
[Parameter]
public string Title { get; set; } = "Set By Child";

[Parameter]
public PanelBody Body { get; set; } =
new()
{
Text = "Set by child.",
Style = "normal"
};
}

2 Aviso

Há suporte para fornecer valores iniciais para os parâmetros de componente, mas


não crie um componente que grava nos próprios parâmetros após ter sido
renderizado pela primeira vez. Para saber mais, confira a seção Parâmetros
substituídos deste artigo.

Os parâmetros Title e Body do componente ParameterChild são definidos por


argumentos na marca HTML que renderiza a instância do componente. O seguinte
componente ParameterParent renderiza dois componentes ParameterChild :

O primeiro componente ParameterChild é renderizado sem fornecer argumentos


de parâmetro.
O segundo componente ParameterChild recebe valores de Title e Body do
componente ParameterParent , que usa uma expressão C# explícita para definir os
valores das propriedades PanelBody .

Pages/ParameterParent.razor :

razor

@page "/parameter-parent"

<h1>Child component (without attribute values)</h1>

<ParameterChild />

<h1>Child component (with attribute values)</h1>


<ParameterChild Title="Set by Parent"
Body="@(new PanelBody() { Text = "Set by parent.", Style =
"italic" })" />

A marcação HTML renderizada a seguir do componente ParameterParent mostra valores


padrão do componente ParameterChild quando o componente ParameterParent não
fornece valores de parâmetro de componente. Quando o componente ParameterParent
fornece valores de parâmetro de componente, eles substituem os valores padrão do
componente ParameterChild .

7 Observação

Para maior clareza, as classes de estilo CSS renderizadas não são mostradas na
marcação HTML renderizada a seguir.

HTML

<h1>Child component (without attribute values)</h1>

<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>

<h1>Child component (with attribute values)</h1>

<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>

Atribua um campo, uma propriedade ou o resultado de um método C# a um parâmetro


de componente como um valor de atributo HTML usando o símbolo reservado @ de
Razor. O seguinte componente ParameterParent2 exibe quatro instâncias do
componente ParameterChild anterior e define os respectivos valores de parâmetro
Title como:

O valor do campo title .


O resultado do método C# GetTitle .
A data local atual em formato longo com ToLongDateString, que usa uma
expressão C# implícita.
O valor da propriedade Title do objeto panelData .
O prefixo @ é necessário para parâmetros de cadeia de caracteres. Caso contrário, a
estrutura pressupõe que um literal de cadeia de caracteres esteja definido.

Fora dos parâmetros de cadeia de caracteres, é recomendável usar o prefixo @ para não
literais, mesmo quando eles não são estritamente necessários.

Não recomendamos o uso do prefixo @ para literais (por exemplo, valores boolianos),
palavras-chave (por exemplo, this ) ou null , mas você poderá optar por usá-los se
desejar. Por exemplo, IsFixed="@true" é incomum, mas tem suporte.

As aspas em torno dos valores de atributo do parâmetro são opcionais na maioria dos
casos, de acordo com a especificação HTML5. Por exemplo, há suporte para Value=this
em vez de Value="this" . No entanto, é recomendável usar aspas, porque é mais fácil de
lembrar e amplamente adotado entre tecnologias baseadas na Web.

Ao longo da documentação, em exemplos de código:

Sempre use aspas. Exemplo: Value="this" .


Não literais sempre usam o prefixo @ , mesmo quando ele é opcional. Exemplos:
Title="@title" , em que title é uma variável de tipo cadeia de caracteres.

Count="@ct" , em que ct é uma variável de tipo de número.

Literais, fora de expressões Razor, sempre evitam @ . Exemplo: IsFixed="true" .

Pages/ParameterParent2.razor :

razor

@page "/parameter-parent-2"

<ParameterChild Title="@title" />

<ParameterChild Title="@GetTitle()" />

<ParameterChild Title="@DateTime.Now.ToLongDateString()" />

<ParameterChild Title="@panelData.Title" />

@code {
private string title = "From Parent field";
private PanelData panelData = new();

private string GetTitle()


{
return "From Parent method";
}

private class PanelData


{
public string Title { get; set; } = "From Parent object";
}
}

7 Observação

Ao atribuir um membro C# a um parâmetro de componente, acrescente como


prefixo ao membro o símbolo @ e nunca prefixe o atributo HTML do parâmetro.

Corrigir:

razor

<ParameterChild Title="@title" />

Incorreto:

razor

<ParameterChild @Title="title" />

Diferente das páginas Razor ( .cshtml ), Blazor não é capaz de executar trabalho
assíncrono em uma expressão Razor ao renderizar um componente. Isso ocorre porque
Blazor foi projetado para renderizar UIs interativas. Em uma interface do usuário
interativa, a tela deve sempre exibir algo, de modo que não faz sentido bloquear o fluxo
de renderização. Em vez disso, o trabalho assíncrono é executado durante um dos
eventos de ciclo de vida assíncronos. Após cada evento de ciclo de vida assíncrono, o
componente pode ser renderizado novamente. A seguinte sintaxe Razornão tem
suporte:

razor

<ParameterChild Title="@await ..." />

O código no exemplo anterior gera um erro do compilador quando o aplicativo é criado:

O operador 'await' só pode ser usado dentro de um método assíncrono. Considere


marcar esse método com o modificador 'async' e alterar seu tipo de retorno para
'Task'.

Para obter um valor para o parâmetro Title no exemplo anterior de maneira


assíncrona, o componente pode usar o evento de ciclo de vida OnInitializedAsync, como
demonstra o seguinte exemplo:

razor

<ParameterChild Title="@title" />

@code {
private string? title;

protected override async Task OnInitializedAsync()


{
title = await ...;
}
}

Para saber mais, consulte Ciclo de vida de renderização de Razor no ASP.NET Core.

Não há suporte para o uso de uma expressão Razor explícita para concatenar texto com
um resultado de expressão para atribuição a um parâmetro. O exemplo a seguir busca
concatenar o texto " Set by " com o valor da propriedade de um objeto. Embora essa
sintaxe tenha suporte em uma página Razor ( .cshtml ), ela não é válida para atribuição
ao parâmetro Title do filho em um componente. A seguinte sintaxe Razornão tem
suporte:

razor

<ParameterChild Title="Set by @(panelData.Title)" />

O código no exemplo anterior gera um erro do compilador quando o aplicativo é criado:

Os atributos de componente não dão suporte a conteúdo complexo (C# misto e


marcação).

Para dar suporte à atribuição de um valor composto, use um método, um campo ou


uma propriedade. O seguinte exemplo executa a concatenação de " Set by " e o valor
da propriedade de um objeto no método C# GetTitle :

Pages/ParameterParent3.razor :

razor

@page "/parameter-parent-3"

<ParameterChild Title="@GetTitle()" />

@code {
private PanelData panelData = new();

private string GetTitle() => $"Set by {panelData.Title}";

private class PanelData


{
public string Title { get; set; } = "Parent";
}
}

Para obter mais informações, consulte a Referência da sintaxe Razor para ASP.NET Core.

2 Aviso

Há suporte para fornecer valores iniciais para os parâmetros de componente, mas


não crie um componente que grava nos próprios parâmetros após ter sido
renderizado pela primeira vez. Para saber mais, confira a seção Parâmetros
substituídos deste artigo.

Os parâmetros de componente devem ser declarados como propriedades automáticas, o


que significa que não devem conter lógica personalizada nos acessadores get ou set .
Por exemplo, a seguinte propriedade StartData é uma propriedade automática:

C#

[Parameter]
public DateTime StartData { get; set; }

Não coloque lógica personalizada no acessador get ou set , porque os parâmetros de


componente são puramente destinados ao uso como um canal para um componente
pai fluir informações para um componente filho. Se um acessador set de uma
propriedade de componente filho contiver lógica que cause a repetição da renderização
do componente pai, um loop de renderização infinito ocorrerá.

Para transformar um valor de parâmetro recebido:

Deixe a propriedade de parâmetro como uma propriedade automática para


representar os dados brutos fornecidos.
Crie uma propriedade ou método diferente para fornecer os dados transformados
com base na propriedade do parâmetro.

Substitua OnParametersSetAsync para transformar um parâmetro recebido sempre que


novos dados forem recebidos.
Há suporte para gravar um valor inicial em um parâmetro de componente porque
atribuições de valor inicial não interferem na renderização de componente automática
de Blazor. A seguinte atribuição do local atual DateTime com DateTime.Now para
StartData é uma sintaxe válida em um componente:

C#

[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;

Após a atribuição inicial de DateTime.Now, não atribua um valor a StartData no código


do desenvolvedor. Para saber mais, confira a seção Parâmetros substituídos deste artigo.

Aplique o atributo [EditorRequired] para especificar um parâmetro de componente


necessário. Se um valor de parâmetro não for fornecido, editores ou ferramentas de
build poderão exibir avisos ao usuário. Esse atributo é válido somente em propriedades
também marcadas com o atributo [Parameter]. O EditorRequiredAttribute é imposto em
tempo de design e quando o aplicativo é criado. O atributo não é imposto no runtime e
não garante um valor de parâmetro diferente de null .

C#

[Parameter]
[EditorRequired]
public string? Title { get; set; }

Também há suporte para listas de atributos de linha única:

C#

[Parameter, EditorRequired]
public string? Title { get; set; }

Tuples (documentação da API) têm suporte para parâmetros de componente e tipos


RenderFragment. O seguinte exemplo de parâmetro de componente passa três valores
em um Tuple :

Shared/RenderTupleChild.razor :

C#

<div class="card w-50" style="margin-bottom:15px">


<div class="card-header font-weight-bold"><code>Tuple</code> Card</div>
<div class="card-body">
<ul>
<li>Integer: @Data?.Item1</li>
<li>String: @Data?.Item2</li>
<li>Boolean: @Data?.Item3</li>
</ul>
</div>
</div>

@code {
[Parameter]
public Tuple<int, string, bool>? Data { get; set; }
}

Pages/RenderTupleParent.razor :

C#

@page "/render-tuple-parent"

<h1>Render <code>Tuple</code> Parent</h1>

<RenderTupleChild Data="@data" />

@code {
private Tuple<int, string, bool> data = new(999, "I aim to misbehave.",
true);
}

Somente tuplas sem nome têm suporte para C# 7.0 ou posterior em componentes
Razor. O suporte para tuplas nomeadas em componentes Razor está planejado para
uma versão futura do ASP.NET Core. Para saber mais, confira Blazor Problema do
Transpiler com tuplas nomeadas (dotnet/aspnetcore #28982) .

Aspas ©2005 Universal Pictures : Serenity (Nathan Fillion )

Parâmetros de rota
Os componentes podem especificar parâmetros de rota no modelo de rota da diretiva
@page. O roteador Blazor usa parâmetros de rota para preencher parâmetros de
componente correspondentes.

Há suporte para parâmetros de rota opcionais. No exemplo a seguir, o parâmetro


opcional text atribui o valor do segmento de rota à propriedade do Text componente.
Se o segmento não estiver presente, o valor de Text será definido como " fantastic " no
método de ciclo de vida OnInitialized.

Pages/RouteParameter.razor :
razor

@page "/route-parameter/{text?}"

<h1>Blazor is @Text!</h1>

@code {
[Parameter]
public string? Text { get; set; }

protected override void OnInitialized()


{
Text = Text ?? "fantastic";
}
}

Para obter informações sobre parâmetros de rota catch-all ( {*pageRoute} ), que


capturam caminhos entre vários limites de pasta, consulte Roteamento e navegação de
Blazor no ASP.NET Core.

Fragmentos de renderização de conteúdo filho


Os componentes podem definir o conteúdo de outro componente. O componente de
atribuição fornece o conteúdo entre as marcas de abertura e fechamento do
componente filho.

No exemplo a seguir, o componente RenderFragmentChild tem um parâmetro de


componente ChildContent que representa um segmento da interface do usuário a ser
renderizado como um RenderFragment. A posição de ChildContent marcação do
componente Razor é onde o conteúdo é renderizado na saída HTML final.

Shared/RenderFragmentChild.razor :

razor

<div class="card w-25" style="margin-bottom:15px">


<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
</div>

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}

) Importante
A propriedade que recebe o conteúdo RenderFragment deve ser nomeada
ChildContent por convenção.

Não há suporte para retornos de chamada de evento para RenderFragment.

O componente RenderFragmentParent a seguir fornece conteúdo para renderizar


RenderFragmentChild colocando o conteúdo dentro das marcas de abertura e

fechamento do componente filho.

Pages/RenderFragmentParent.razor :

razor

@page "/render-fragment-parent"

<h1>Render child content</h1>

<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>

Devido à maneira como Blazor renderiza o conteúdo filho, renderizar componentes


dentro de um loop for requer uma variável de índice local se a variável de loop de
incremento é usada no conteúdo do componente RenderFragmentChild . O seguinte
exemplo pode ser adicionado ao componente anterior RenderFragmentParent :

razor

<h1>Three children with an index variable</h1>

@for (int c = 0; c < 3; c++)


{
var current = c;

<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
}

Como alternativa, use um loop foreach com Enumerable.Range em vez de um loop for.
O seguinte exemplo pode ser adicionado ao componente anterior
RenderFragmentParent :

razor
<h1>Second example of three children with an index variable</h1>

@foreach (var c in Enumerable.Range(0,3))


{
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
}

Os fragmentos de renderização são usados para renderizar conteúdo filho em


aplicativos Blazor e são descritos com exemplos nos seguintes artigos e seções de
artigo:

Blazor layouts
Passar dados em uma hierarquia de componentes
Componentes modelados
Tratamento de exceções globais

7 Observação

Os componentes internosRazor da estrutura Blazor usam a mesma convenção de


parâmetro de componente ChildContent para definir seu conteúdo. Você pode ver
os componentes que definem o conteúdo filho pesquisando pelo nome
ChildContent da propriedade de parâmetro do componente na Documentação da

API (filtra a API pelo termo de pesquisa "ChildContent").

Fragmentos de renderização para lógica de


renderização reutilizável
Você pode fatorar componentes filho puramente como uma forma de reutilizar a lógica
de renderização. No bloco @code de qualquer componente, defina um RenderFragment
e renderize o fragmento de qualquer local quantas vezes forem necessárias:

razor

<h1>Hello, world!</h1>

@RenderWelcomeInfo

<p>Render the welcome info a second time:</p>

@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = __builder =>
{
<p>Welcome to your new app!</p>
};
}

Para obter mais informações, consulte Reutilizar a lógica de renderização.

Parâmetros substituídos
A estrutura Blazor geralmente impõe uma atribuição segura de parâmetro pai para filho:

Os parâmetros não são substituídos inesperadamente.


Os efeitos colaterais são minimizados. Por exemplo, renderizações adicionais são
evitadas porque podem criar loops de renderização infinitos.

Um componente filho recebe novos valores de parâmetro que possivelmente


substituem valores existentes quando o componente pai é renderizado novamente. A
substituição acidental de valores de parâmetro em um componente filho geralmente
ocorre ao desenvolver o componente com um ou mais parâmetros associados a dados,
quando o desenvolvedor grava diretamente em um parâmetro no filho:

O componente filho é renderizado com um ou mais valores de parâmetro do


componente pai.
O filho grava diretamente no valor de um parâmetro.
O componente pai é renderizado novamente e substitui o valor do parâmetro
filho.

O potencial de substituição de valores de parâmetro também se estende aos


acessadores da propriedade set do componente filho.

) Importante

Nossa diretriz geral é de não criar componentes que gravam diretamente nos
próprios parâmetros depois que o componente é renderizado pela primeira vez.

Considere o seguinte componente Expander que:

Renderiza o conteúdo filho.


Alterna a exibição de conteúdo filho com um parâmetro de componente
( Expanded ).
Depois que o componente Expander a seguir demonstra um parâmetro substituído, um
componente Expander modificado é mostrado para demonstrar a abordagem correta
para esse cenário. Os exemplos a seguir podem ser colocados em um aplicativo de
exemplo local para experimentar os comportamentos descritos.

Shared/Expander.razor :

razor

<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">


<div class="card-body">
<h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)
</h2>

@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>

@code {
[Parameter]
public bool Expanded { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }

private void Toggle()


{
Expanded = !Expanded;
}
}

O componente Expander é adicionado ao seguinte componente pai ExpanderExample


que pode chamar StateHasChanged:

Chamar StateHasChanged no código do desenvolvedor notifica um componente


de que seu estado foi alterado e normalmente dispara a nova renderização do
componente para atualizar a interface do usuário. StateHasChanged será abordado
com mais detalhes posteriormente em Ciclo de vida de componentes Razor no
ASP.NET Core e Renderização de componentes Razor no ASP.NET Core.
O atributo de diretiva do botão @onclick anexa um manipulador de eventos ao
evento onclick do botão. A manipulação de eventos será abordada com mais
detalhes posteriormente em Manipulação de eventos de Blazor no ASP.NET Core.

Pages/ExpanderExample.razor :
razor

@page "/expander-example"

<Expander Expanded="true">
Expander 1 content
</Expander>

<Expander Expanded="true" />

<button @onclick="StateHasChanged">
Call StateHasChanged
</button>

Inicialmente, os componentes Expander se comportam de maneira independente


quando suas propriedades Expanded são alternadas. Os componentes filho mantêm
seus estados, conforme o esperado.

Se StateHasChanged for chamado em um componente pai, a estrutura Blazor


renderizará novamente os componentes filho se os parâmetros deles puderem ter sido
alterados:

Para um grupo de tipos de parâmetro que Blazor verifica explicitamente, Blazor


renderiza novamente um componente filho quando detecta que qualquer um dos
parâmetros foi alterado.
Para tipos de parâmetro não verificados, Blazor renderiza novamente o
componente filho independentemente de os parâmetros terem sido alterados. O
conteúdo filho se enquadra nessa categoria de tipos de parâmetro porque é do
tipo RenderFragment, que é um delegado que se refere a outros objetos mutáveis.

Para o componente ExpanderExample :

O primeiro componente Expander define o conteúdo filho em um valor


potencialmente mutável RenderFragment, portanto, uma chamada para
StateHasChanged no componente pai renderiza novamente, de maneira
automática, o componente e potencialmente substitui o valor de Expanded para
seu valor inicial de true .
O segundo componente Expander não define o conteúdo filho. Portanto, um
RenderFragment potencialmente mutável não existe. Uma chamada para
StateHasChanged no componente pai não renderiza novamente, de maneira
automática, o componente filho, portanto o valor do componente Expanded não é
substituído.
Para manter o estado no cenário anterior, use um campo privado no componente
Expander para manter o estado de alternância.

O seguinte componente Expander revisado:

Aceita o valor do parâmetro de componente Expanded do pai.


Atribui o valor do parâmetro de componente a um campo privado ( expanded ) no
evento OnInitialized.
Usa o campo privado para manter o estado de alternância interno, que demonstra
como evitar gravar diretamente em um parâmetro.

7 Observação

A orientação nesta seção se estende à lógica semelhante em acessadores set de


parâmetro de componente, o que pode resultar em efeitos colaterais indesejáveis
semelhantes.

Shared/Expander.razor :

razor

<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">


<div class="card-body">
<h2 class="card-title">Toggle (<code>expanded</code> = @expanded)
</h2>

@if (expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>

@code {
private bool expanded;

[Parameter]
public bool Expanded { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }

protected override void OnInitialized()


{
expanded = Expanded;
}

private void Toggle()


{
expanded = !expanded;
}
}

Para ver exemplos de associação pai-filho bidirecional, consulte Associação de dados do


ASP.NET Core Blazor. Para obter informações adicionais, consulte Erro de associação
bidirecional de Blazor (dotnet/aspnetcore #24599) .

Para obter mais informações sobre a detecção de alterações, incluindo informações


sobre os tipos exatos que Blazor verifica, consulte Renderização de componentes de
Razor ASP.NET Core.

Nivelamento de atributos e parâmetros


arbitrários
Os componentes podem capturar e renderizar atributos adicionais além dos parâmetros
declarados do componente. Atributos adicionais podem ser capturados em um
dicionário e nivelados em um elemento quando o componente é renderizado usando o
atributo de diretiva @attributes do Razor. Esse cenário é útil para definir um
componente que produz um elemento de marcação com suporte para uma variedade
de personalizações. Por exemplo, pode ser maçante definir atributos separadamente
para um <input> com suporte para muitos parâmetros.

No seguinte componente Splat :

O primeiro elemento <input> ( id="useIndividualParams" ) usa parâmetros de


componente individuais.
O segundo elemento <input> ( id="useAttributesDict" ) usa nivelamento de
atributo.

Pages/Splat.razor :

razor

@page "/splat"

<input id="useIndividualParams"
maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />

<input id="useAttributesDict"
@attributes="InputAttributes" />
@code {
private string maxlength = "10";
private string placeholder = "Input placeholder text";
private string required = "required";
private string size = "50";

private Dictionary<string, object> InputAttributes { get; set; } =


new()
{
{ "maxlength", "10" },
{ "placeholder", "Input placeholder text" },
{ "required", "required" },
{ "size", "50" }
};
}

Os elementos <input> renderizados na página da Web são idênticos:

HTML

<input id="useIndividualParams"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">

<input id="useAttributesDict"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">

Para aceitar atributos arbitrários, defina um parâmetro de componente com a


propriedade CaptureUnmatchedValues definida como true :

razor

@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? InputAttributes { get; set; }
}

A propriedade CaptureUnmatchedValues em [Parameter] permite que o parâmetro faça


a correspondência com todos os atributos que não correspondem a nenhum outro
parâmetro. Um componente só pode definir um parâmetro com
CaptureUnmatchedValues. O tipo de propriedade usado com CaptureUnmatchedValues
deve ser atribuível com base em Dictionary<string, object> com chaves de cadeia de
caracteres. O uso de IEnumerable<KeyValuePair<string, object>> ou
IReadOnlyDictionary<string, object> também são opções neste cenário.

A posição de @attributes em relação à posição dos atributos de elemento é importante.


Quando @attributes são nivelados no elemento, os atributos são processados da direita
para a esquerda (do último ao primeiro). Considere o seguinte exemplo de um
componente pai que consome um componente filho:

Shared/AttributeOrderChild1.razor :

razor

<div @attributes="AdditionalAttributes" extra="5" />

@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

Pages/AttributeOrderParent1.razor :

razor

@page "/attribute-order-parent-1"

<AttributeOrderChild1 extra="10" />

O atributo extra do componente AttributeOrderChild1 é definido à direita de


@attributes. O <div> renderizado do componente AttributeOrderParent1 contém
extra="5" quando passado pelo atributo adicional, porque os atributos são
processados da direita para a esquerda (do último ao primeiro):

HTML

<div extra="5" />

No seguinte exemplo, a ordem de extra e @attributes é invertida no componente filho


<div> :

Shared/AttributeOrderChild2.razor :

razor

<div extra="5" @attributes="AdditionalAttributes" />


@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

Pages/AttributeOrderParent2.razor :

razor

@page "/attribute-order-parent-2"

<AttributeOrderChild2 extra="10" />

O <div> na página da Web renderizada do componente pai contém extra="10" quando


passado pelo atributo adicional:

HTML

<div extra="10" />

Capturar referências a componentes


Referências a componentes proporcionam uma maneira de fazer referência a uma
instância de componente para emitir comandos. Para capturar uma referência a um
componente:

Adicione um atributo @ref ao componente filho.


Defina um campo com o mesmo tipo que o componente filho.

Quando o componente é renderizado, o campo é preenchido com a instância do


componente. Em seguida, você pode invocar métodos .NET na instância.

Considere o componente ReferenceChild a seguir que registra uma mensagem quando


seu ChildMethod é chamado.

Shared/ReferenceChild.razor :

razor

@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger

@code {
public void ChildMethod(int value)
{
logger.LogInformation("Received {Value} in ChildMethod", value);
}
}

Uma referência de componente só é preenchida depois que o componente é


renderizado e sua saída inclui o elemento de ReferenceChild . Até que o componente
seja renderizado, não há nada a que fazer referência.

Para manipular referências de componente após a renderização do componente, use os


métodos OnAfterRender ou OnAfterRenderAsync.

Para usar uma variável de referência com um manipulador de eventos, use uma
expressão lambda ou atribua o delegado do manipulador de eventos nos métodos
OnAfterRender ou OnAfterRenderAsync. Isso garante que a variável de referência seja
atribuída antes que o manipulador de eventos seja atribuído.

A abordagem lambda a seguir usa o componente ReferenceChild anterior.

Pages/ReferenceParent1.razor :

razor

@page "/reference-parent-1"

<button @onclick="@(() => childComponent?.ChildMethod(5))">


Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />

@code {
private ReferenceChild? childComponent;
}

A abordagem de delegado a seguir usa o componente ReferenceChild anterior.

Pages/ReferenceParent2.razor :

razor

@page "/reference-parent-2"

<button @onclick="@(() => callChildMethod?.Invoke())">


Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />


@code {
private ReferenceChild? childComponent;
private Action? callChildMethod;

protected override void OnAfterRender(bool firstRender)


{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}

private void CallChildMethod()


{
childComponent?.ChildMethod(5);
}
}

Embora a captura de referências de componentes use uma sintaxe semelhante para


capturar referências de elementos, capturar referências de componentes não é um
recurso de interoperabilidade de JavaScript. As referências de componente não são
passadas para o código JavaScript. As referências de componente são usadas apenas no
código .NET.

) Importante

Não use referências de componente para alterar o estado dos componentes filho.
Em vez disso, use parâmetros de componente declarativos normais para passar
dados para componentes filho. O uso de parâmetros de componente resulta em
componentes filho que são renderizados novamente, nos horários corretos,
automaticamente. Para obter mais informações, consulte a seção parâmetros de
componente e o artigo Associação de dados Blazor do ASP.NET Core.

Contexto de sincronização
Blazor usa um contexto de sincronização (SynchronizationContext) para impor um
thread lógico de execução. Os métodos de ciclo de vida e os retornos de chamada de
evento de componente gerados por Blazor são executados no contexto de
sincronização.

O contexto de sincronização de Blazor Server tenta emular um ambiente de thread


único para que ele corresponda de perto ao modelo WebAssembly no navegador, que é
de thread único. Em qualquer dado momento, o trabalho é executado exatamente em
um thread, o que gera a impressão de um só thread lógico. Nenhuma operação é
executada simultaneamente.

Evitar chamadas de bloqueio de thread


Geralmente, não chame os métodos a seguir nos componentes. Os seguintes métodos
bloqueiam o thread de execução e, portanto, impedem que o aplicativo retome o
trabalho até que o Task subjacente seja concluído:

Result
Wait
WaitAny
WaitAll
Sleep
GetResult

7 Observação

Exemplos de documentação de Blazor que usam os métodos de bloqueio de


thread mencionados nesta seção estão usando os métodos apenas para fins de
demonstração, não como diretrizes de codificação recomendadas. Por exemplo,
algumas demonstrações de código de componente simulam um processo de
execução longa chamando Thread.Sleep.

Invocar métodos de componente externamente para


atualizar o estado
Caso um componente precise ser atualizado com base em um evento externo, como um
temporizador ou outra notificação, use o método InvokeAsync , que envia a execução de
código para o contexto de sincronização de Blazor. Por exemplo, considere o serviço de
notificador a seguir, que pode notificar qualquer componente de escuta sobre o estado
atualizado. O método Update pode ser chamado de qualquer lugar do aplicativo.

TimerService.cs :

C#

public class TimerService : IDisposable


{
private int elapsedCount;
private readonly static TimeSpan heartbeatTickRate =
TimeSpan.FromSeconds(5);
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private PeriodicTimer? timer;

public TimerService(NotifierService notifier,


ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}

public async Task Start()


{
if (timer is null)
{
timer = new(heartbeatTickRate);
logger.LogInformation("Started");

using (timer)
{
while (await timer.WaitForNextTickAsync())
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation($"elapsedCount: {elapsedCount}");
}
}
}
}

public void Dispose()


{
timer?.Dispose();
}
}

NotifierService.cs :

C#

public class NotifierService


{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}

public event Func<string, int, Task>? Notify;


}
Registre os serviços:

Em um aplicativo Blazor WebAssembly, registre os serviços como singletons em


Program.cs :

C#

builder.Services.AddSingleton<NotifierService>();
builder.Services.AddSingleton<TimerService>();

Em um aplicativo Blazor Server, registre os serviços como com escopo em


Program.cs :

C#

builder.Services.AddScoped<NotifierService>();
builder.Services.AddScoped<TimerService>();

Use o NotifierService para atualizar um componente.

Pages/ReceiveNotifications.razor :

razor

@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer

<h1>Receive Notifications</h1>

<h2>Timer Service</h2>

<button @onclick="StartTimer">Start Timer</button>

<h2>Notifications</h2>

<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting first notification</span>
}
</p>

@code {
private (string key, int value) lastNotification;

protected override void OnInitialized()


{
Notifier.Notify += OnNotify;
}

public async Task OnNotify(string key, int value)


{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}

private async Task StartTimer()


{
await Timer.Start();
}

public void Dispose()


{
Notifier.Notify -= OnNotify;
}
}

No exemplo anterior:

NotifierService invoca o método OnNotify do componente fora do contexto de

sincronização de Blazor. InvokeAsync é usado para mudar para o contexto correto


e enfileirar uma renderização. Para saber mais, consulte Renderização de
componentes de Razor no ASP.NET Core.
O componente implementa IDisposable. A assinatura do delegado OnNotify é
cancelada no método Dispose , que é chamado pela estrutura quando o
componente é descartado. Para saber mais, consulte Ciclo de vida de renderização
de Razor no ASP.NET Core.

Usar @key para controlar a preservação de


elementos e componentes
Ao renderizar uma lista de elementos ou componentes e os elementos ou componentes
serem alterados posteriormente, Blazor precisa decidir qual dos elementos ou
componentes anteriores podem ser retidos e como os objetos de modelo devem ser
mapeados para eles. Normalmente, esse processo é automático e pode ser ignorado,
mas há casos em que você pode querer controlá-lo.
Considere os seguintes componentes Details e People :

O componente Details recebe dados ( Data ) do componente People pai, que são
exibidos em um elemento <input> . Qualquer elemento <input> exibido pode
receber o foco da página do usuário ao selecionar um dos elementos <input> .
O componente People cria uma lista de objetos de pessoa para exibição usando o
componente Details . A cada três segundos, uma nova pessoa é adicionada à
coleção.

Essa demonstração permite:

Selecionar um <input> entre vários componentes Details renderizados.


Estudar o comportamento do foco da página à medida que a coleção de pessoas
cresce automaticamente.

Shared/Details.razor :

razor

<input value="@Data" />

@code {
[Parameter]
public string? Data { get; set; }
}

No componente People a seguir, cada iteração de adicionar uma pessoa em


OnTimerCallback resulta na recompilação por Blazor de toda a coleção. O foco da
página permanece na mesma posição de índice dos elementos <input> , portanto, o foco
muda sempre que uma pessoa é adicionada. Desviar o foco do que o usuário selecionou
não é um comportamento desejável. Depois de demonstrar o comportamento ruim com
o componente a seguir, o atributo de diretiva @key é usado para aprimorar a
experiência do usuário.

Pages/People.razor :

razor

@page "/people"
@using System.Timers
@implements IDisposable

@foreach (var person in people)


{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);

public List<Person> people =


new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};

protected override void OnInitialized()


{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}

private void OnTimerCallback()


{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss
tt")}"
});
StateHasChanged();
});
}

public void Dispose() => timer.Dispose();

public class Person


{
public string? Data { get; set; }
}
}

O conteúdo da coleção people é alterado com entradas inseridas, excluídas ou


reordenadas. A nova renderização pode levar a diferenças de comportamento visíveis.
Sempre que uma pessoa é inserida na coleção people , o elemento anterior ao elemento
focado no momento recebe o foco. O foco do usuário é perdido.

O processo de mapeamento de elementos ou componentes para uma coleção pode ser


controlado com o atributo de diretiva @key. Uso de @key garante a preservação de
elementos ou componentes com base no valor da chave. Se o componente Details no
exemplo anterior for inserido no item person , Blazor ignorará a renderização de
componentes Details que não foram alterados.
Para modificar o componente People para usar o atributo de diretiva @key com a
coleção people , atualize o elemento <Details> para o seguinte:

razor

<Details @key="person" Data="@person.Data" />

Quando a coleção people é alterada, a associação entre instâncias de Details e de


person é mantida. Quando um Person é inserido no início da coleção, uma nova
instância de Details é inserida nessa posição correspondente. Outras instâncias
permanecem inalteradas. Assim, o foco do usuário não é perdido à medida que pessoas
são adicionadas à coleção.

Outras atualizações de coleção exibem o mesmo comportamento quando o atributo de


diretiva @key é usado:

Se uma instância for excluída da coleção, somente a instância de componente


correspondente será removida da interface do usuário. Outras instâncias
permanecem inalteradas.
Se as entradas da coleção forem reordenadas, as instâncias de componente
correspondentes serão preservadas e reordenadas na interface do usuário.

) Importante

As chaves são locais para cada elemento ou componente de contêiner. Elas não são
comparadas globalmente no documento.

Quando usar @key


Normalmente, faz sentido usar @key sempre que uma lista é renderizada (por exemplo,
em um bloco foreach) e existe um valor adequado para definir o @key.

Você também pode usar @key para preservar uma subárvore de elemento ou
componente quando um objeto não é alterado, como mostram os exemplos a seguir.

Exemplo 1:

razor

<li @key="person">
<input value="@person.Data" />
</li>
Exemplo 2:

razor

<div @key="person">
@* other HTML elements *@
</div>

Se uma instância de person for alterada, a diretiva de atributo @key forçará Blazor a:

Descartar todo o <li> ou <div> e seus descendentes.


Recompilar a subárvore dentro da interface do usuário com novos elementos e
componentes.

Isso é útil para garantir que nenhum estado da interface do usuário seja preservado
quando a coleção for alterada em uma subárvore.

Escopo de @key
A diretiva de atributo @key tem como escopo os próprios irmãos dentro do elemento
pai.

Considere o exemplo a seguir. As chaves first e second são comparadas entre si


dentro do mesmo escopo do elemento <div> externo:

razor

<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>

O exemplo a seguir demonstra as chaves first e second em nos próprios escopos, não
relacionadas uma à outra e sem influência uma sobre a outra. Cada escopo de @key se
aplica apenas ao elemento <div> pai, e não entre os elementos <div> pai:

razor

<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
Para o componente Details mostrado anteriormente, os seguintes exemplos
renderizam dados person dentro do mesmo escopo @key e demonstram casos de uso
típicos para @key:

razor

<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>

razor

@foreach (var person in people)


{
<div @key="person">
<Details Data="@person.Data" />
</div>
}

razor

<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>

Os exemplos a seguir só têm escopo @key para o elemento <div> ou <li> que envolve
cada instância do componente Details . Portanto, os dados person de cada membro da
coleção people não são inseridos em cada instância de person entre os componentes
Details renderizados. Evite os seguintes padrões ao usar @key:

razor

@foreach (var person in people)


{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}
razor

<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>

Quando não usar @key


Há um custo quanto ao desempenho ao renderizar com @key. O custo de desempenho
não é grande, mas só especifique @key se a preservação do elemento ou componente
beneficiar o aplicativo.

Mesmo que @key não seja usado, Blazor preserva o máximo possível as instâncias de
componente e de elemento filho. A única vantagem de usar @key é o controle sobre
como as instâncias de modelo são mapeadas para as instâncias de componente
preservadas, em vez de Blazor selecionar o mapeamento.

Valores a serem usados para @key


Geralmente, faz sentido fornecer um dos seguintes valores para @key:

Instâncias de objetos de modelo. Por exemplo, a instância Person ( person ) foi


usada no exemplo anterior. Isso garante a preservação baseada na igualdade de
referência do objeto.
Identificadores exclusivos. Por exemplo, identificadores exclusivos podem ser
baseados nos valores de chave primária do tipo int , string ou Guid .

Certifique-se de que os valores usados para @key não entrem em conflito. Se valores
conflitantes forem detectados dentro do mesmo elemento pai, Blazor gerará uma
exceção porque não poderá mapear de modo determinístico elementos ou
componentes antigos para elementos ou componentes novos. Use apenas valores
distintos, como instâncias de objeto ou valores de chave primária.

Aplicar um atributo
Os atributos podem ser aplicados a componentes com a diretiva @attribute. O seguinte
exemplo aplica o atributo [Authorize] à classe do componente:
razor

@page "/"
@attribute [Authorize]

Atributos de elemento HTML condicionais


As propriedades de atributo do elemento HTML são definidas condicionalmente com
base no valor do .NET. Se o valor for false ou null , a propriedade não estará definida.
Se o valor for true , a propriedade estará definida.

No exemplo a seguir, IsCompleted determina se a propriedade checked do elemento


<input> está definida.

Pages/ConditionalAttribute.razor :

razor

@page "/conditional-attribute"

<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>

<button @onclick="@(() => IsCompleted = !IsCompleted)">


Change IsCompleted
</button>

@code {
[Parameter]
public bool IsCompleted { get; set; }
}

Para obter mais informações, consulte a Referência da sintaxe Razor para ASP.NET Core.

2 Aviso

Alguns atributos HTML, como aria-pressed , não funcionam corretamente


quando o tipo do .NET é bool . Nesses casos, use um tipo string em vez de um
bool .

HTML bruto
Normalmente, as cadeias de caracteres são renderizadas usando nós de texto DOM, o
que significa que qualquer marcação que elas possam conter é ignorada e tratada como
texto literal. Para renderizar HTML bruto, encapsule o conteúdo HTML em um valor
MarkupString. O valor é analisado como HTML ou SVG e inserido no DOM.

2 Aviso

Renderizar HTML bruto construído de qualquer fonte não confiável é um risco de


segurança e sempre deve ser evitado.

O exemplo a seguir mostra o uso do tipo MarkupString para adicionar um bloco de


conteúdo HTML estático à saída renderizada de um componente.

Pages/MarkupStringExample.razor :

razor

@page "/markup-string-example"

@((MarkupString)myMarkup)

@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup
string</em>.</p>";
}

Modelos Razor
Fragmentos de renderização podem ser definidos usando a sintaxe do modelo Razor
para definir um snippet de interface do usuário. Os modelos Razor usam o seguinte
formato:

razor

@<{HTML tag}>...</{HTML tag}>

O exemplo a seguir ilustra como especificar valores de RenderFragment e


RenderFragment<TValue> e renderizar modelos diretamente em um componente.
Fragmentos de renderização também podem ser passados como argumentos para
componentes de modelo.

Pages/RazorTemplate.razor :
razor

@page "/razor-template"

@timeTemplate

@petTemplate(new Pet { Name = "Nutty Rex" })

@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.
</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet:
@pet.Name</p>;

private class Pet


{
public string? Name { get; set; }
}
}

Saída renderizada do código anterior:

HTML

<p>The time is 4/19/2021 8:54:46 AM.</p>


<p>Pet: Nutty Rex</p>

Ativos estáticos
Blazor segue a convenção dos aplicativos ASP.NET Core para ativos estáticos. Ativos
estáticos ficam localizados na pasta web root (wwwroot) ou nas pastas do projeto, sob a
pasta wwwroot .

Use um caminho relativo à base ( / ) para se referir à raiz da Web de um ativo estático.
No exemplo a seguir, logo.png está localizado fisicamente na pasta {PROJECT
ROOT}/wwwroot/images . {PROJECT ROOT} é a raiz do projeto do aplicativo.

razor

<img alt="Company logo" src="/images/logo.png" />

Os componentes não dão suporte à notação com til e barra ( ~/ ).

Para saber como definir o caminho base de um aplicativo, consulte Hospedar e


implantar ASP.NET CoreBlazor.
Não há suporte para Auxiliares de Marca nos
componentes
Não há suporte para Tag Helpers nos componentes. Para fornecer funcionalidade
semelhante à do Auxiliar de Marca em Blazor, crie um componente com a mesma
funcionalidade que ele e use o componente em vez disso.

Imagens de gráfico vetorial escalonável (SVG)


Como Blazor renderiza HTML, imagens com suporte de navegador, incluindo imagens
SVG (gráficos vetoriais escalonáveis) (.svg) , têm suporte por meio da marca <img> :

HTML

<img alt="Example image" src="image.svg" />

De maneira semelhante, há suporte para imagens SVG nas regras CSS de um arquivo de
folha de estilos ( .css ):

css

.element-class {
background-image: url("image.svg");
}

Blazor dá suporte ao elemento <foreignObject> para exibir HTML arbitrário em um


SVG. A marcação pode representar HTML arbitrário, um RenderFragment ou um
componente Razor.

O exemplo a seguir demonstra:

Exibição de um string ( @message ).


Vinculação bidirecional com um elemento <input> e um campo value .
Um componente Robot .

razor

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">


<rect x="0" y="0" rx="10" ry="10" width="200" height="200"
stroke="black"
fill="none" />
<foreignObject x="20" y="20" width="160" height="160">
<p>@message</p>
</foreignObject>
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="200" height="200">
<label>
Two-way binding:
<input @bind="value" @bind:event="oninput" />
</label>
</foreignObject>
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<Robot />
</foreignObject>
</svg>

@code {
private string message = "Lorem ipsum dolor sit amet, consectetur
adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua.";

private string? value;


}

Comportamento de renderização de espaço em


branco
A menos que a diretiva @preservewhitespace seja usada com um valor de true , o
espaço em branco extra será removido por padrão se:

Estiver à esquerda ou à direita dentro de um elemento.


Estiver à esquerda ou à direita dentro de um parâmetro
RenderFragment/RenderFragment<TValue> (por exemplo, conteúdo filho passado
para outro componente).
Preceder ou seguir um bloco de código C#, como @if ou @foreach .

A remoção do espaço em branco pode afetar a saída renderizada ao usar uma regra de
CSS, como white-space: pre . Para desabilitar essa otimização de desempenho e
preservar o espaço em branco, execute uma das seguintes ações:

Adicione a diretiva @preservewhitespace true na parte superior do arquivo Razor


( .razor ) para aplicar a preferência a um componente específico.
Adicione a diretiva @preservewhitespace true dentro de um arquivo
_Imports.razor para aplicar a preferência a um subdiretório ou a todo o projeto.
Na maioria dos casos, nenhuma ação é necessária, pois os aplicativos normalmente
continuam se comportando normalmente (mas com mais rapidez). Se a remoção de
espaço em branco causar um problema de renderização para um componente
específico, use @preservewhitespace true nesse componente para desabilitar essa
otimização.

Suporte para parâmetro de tipo genérico


A diretiva @typeparam declara um parâmetro de tipo genérico para a classe de
componente gerada:

razor

@typeparam TItem

Há suporte para a sintaxe C# com restrições de tipo where:

razor

@typeparam TEntity where TEntity : IEntity

No exemplo a seguir, o componente ListGenericTypeItems1 é de tipo genérico como


TExample .

Shared/ListGenericTypeItems1.razor :

razor

@typeparam TExample

@if (ExampleList is not null)


{
<ul>
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[Parameter]
public IEnumerable<TExample>? ExampleList{ get; set; }
}
O seguinte componente GenericTypeExample1 renderiza dois componentes
ListGenericTypeItems1 :

Dados de cadeia de caracteres ou inteiros são atribuídos ao parâmetro


ExampleList de cada componente.
O tipo string ou int que corresponde ao tipo de dados atribuído é definido para
o parâmetro de tipo ( TExample ) de cada componente.

Pages/GenericTypeExample1.razor :

razor

@page "/generic-type-example-1"

<h1>Generic Type Example 1</h1>

<ListGenericTypeItems1 ExampleList="@(new List<string> { "Item 1", "Item 2"


})"
TExample="string" />

<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })"


TExample="int" />

Para obter mais informações, consulte a Referência da sintaxe Razor para ASP.NET Core.
Para ver um exemplo de tipo genérico com componentes com modelo, consulte
Componentes com modelo Blazor no ASP.NET Core.

Suporte para tipo genérico em cascata


Um componente ancestral pode colocar em cascata, por nome, para os descendentes
um parâmetro de tipo usando o atributo [CascadingTypeParameter]. Esse atributo
permite que uma inferência de tipo genérico use o parâmetro de tipo especificado
automaticamente com descendentes que têm um parâmetro de tipo com o mesmo
nome.

Ao adicionar @attribute [CascadingTypeParameter(...)] a um componente, o


argumento de tipo genérico especificado é usado automaticamente por descendentes
que:

São aninhados como conteúdo filho para o componente no mesmo documento


.razor .
Também declaram um @typeparam com o mesmo nome.
Não têm outro valor fornecido explicitamente ou inferido implicitamente para o
parâmetro de tipo. Se outro valor for inferido ou fornecido, ele terá precedência
sobre o tipo genérico em cascata.

Ao receber um parâmetro de tipo em cascata, os componentes obtêm o valor do


parâmetro do ancestral mais próximo que tem um CascadingTypeParameterAttribute
com um nome correspondente. Parâmetros de tipo genérico em cascata são
substituídos em uma subárvore específica.

A correspondência é realizada apenas pelo nome. Portanto, recomendamos evitar um


parâmetro de tipo genérico em cascata com um nome genérico, por exemplo, T ou
TItem . Se um desenvolvedor optar por colocar em cascata um parâmetro de tipo, ele

estará implicitamente prometendo que o nome é exclusivo o suficiente para não entrar
em conflito com outros parâmetros de tipo em cascata de componentes não
relacionados.

Os tipos genéricos podem ser colocados em cascata para componentes filho em


qualquer uma das seguintes abordagens com componentes ancestrais (pai), que são
demonstrados nas duas seguintes subseções:

Definir explicitamente o tipo genérico em cascata.


Inferir o tipo genérico em cascata.

As subseções a seguir fornecem exemplos das abordagens anteriores usando os dois


componentes ListDisplay a seguir. Os componentes recebem e renderizam dados de
lista e têm como tipo genérico TExample . Esses componentes são para fins de
demonstração e diferem apenas na cor do texto em que a lista é renderizada. Se quiser
experimentar os componentes nas subseções a seguir em um aplicativo de teste local,
primeiro adicione os dois componentes a seguir ao aplicativo.

Shared/ListDisplay1.razor :

razor

@typeparam TExample

@if (ExampleList is not null)


{
<ul style="color:blue">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
}

Shared/ListDisplay2.razor :

razor

@typeparam TExample

@if (ExampleList is not null)


{
<ul style="color:red">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
}

Tipos genéricos explícitos baseados em componentes


ancestrais
A demonstração nesta seção coloca um tipo em cascada explicitamente para TExample .

7 Observação

Esta seção usa os dois componentes ListDisplay na seção Suporte para tipo
genérico em cascata.

O componente ListGenericTypeItems2 a seguir recebe dados e coloca em cascata um


parâmetro de tipo genérico chamado TExample para os componentes descendentes. No
próximo componente pai, o componente ListGenericTypeItems2 é usado para exibir
dados de lista com o componente ListDisplay anterior.

Shared/ListGenericTypeItems2.razor :

razor

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample
<h2>List Generic Type Items 2</h2>

@ChildContent

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}

O componente pai GenericTypeExample2 a seguir define o conteúdo filho


(RenderFragment) de dois componentes ListGenericTypeItems2 que especificam os
tipos ListGenericTypeItems2 ( TExample ), que são em colocados em cascata para os
componentes filho. Os componentes ListDisplay são renderizados com os dados do
item de lista mostrados no exemplo. Os dados de cadeia de caracteres são usados com
o primeiro componente ListGenericTypeItems2 , e os dados inteiros são usados com o
segundo componente ListGenericTypeItems2 .

Pages/GenericTypeExample2.razor :

razor

@page "/generic-type-example-2"

<h1>Generic Type Example 2</h1>

<ListGenericTypeItems2 TExample="string">
<ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })"
/>
<ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })"
/>
</ListGenericTypeItems2>

<ListGenericTypeItems2 TExample="int">
<ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" />
<ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" />
</ListGenericTypeItems2>

Especificar o tipo explicitamente também permite o uso de valores e parâmetros em


cascata para fornecer dados a componentes filho, como mostra a demonstração a
seguir.

Shared/ListDisplay3.razor :

razor

@typeparam TExample
@if (ExampleList is not null)
{
<ul style="color:blue">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
}

Shared/ListDisplay4.razor :

razor

@typeparam TExample

@if (ExampleList is not null)


{
<ul style="color:red">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
}

Shared/ListGenericTypeItems3.razor :

razor

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Generic Type Items 3</h2>

@ChildContent

@if (ExampleList is not null)


{
<ul style="color:green">
@foreach(var item in ExampleList)
{
<li>@item</li>
}
</ul>

<p>
Type of <code>TExample</code>: @typeof(TExample)
</p>
}

@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }
}

Ao colocar em cascata os dados no exemplo a seguir, o tipo precisa ser fornecido ao


componente ListGenericTypeItems3 .

Pages/GenericTypeExample3.razor :

razor

@page "/generic-type-example-3"

<h1>Generic Type Example 3</h1>

<CascadingValue Value="@stringData">
<ListGenericTypeItems3 TExample="string">
<ListDisplay3 />
<ListDisplay4 />
</ListGenericTypeItems3>
</CascadingValue>

<CascadingValue Value="@integerData">
<ListGenericTypeItems3 TExample="int">
<ListDisplay3 />
<ListDisplay4 />
</ListGenericTypeItems3>
</CascadingValue>

@code {
private List<string> stringData = new() { "Item 1", "Item 2" };
private List<int> integerData = new() { 1, 2, 3 };
}

Quando vários tipos genéricos são colocados em cascata, os valores para todos eles no
conjunto devem ser passados. No exemplo a seguir, TItem , TValue e TEdit são tipos
genéricos GridColumn , mas o componente pai que coloca GridColumn não especifica o
tipo TItem :

razor

<GridColumn TValue="string" TEdit="@TextEdit" />

O exemplo anterior gera um erro em tempo de compilação em que o componente


GridColumn está sem o parâmetro de tipo TItem . O código válido especifica todos os
tipos:

razor

<GridColumn TValue="string" TEdit="@TextEdit" TItem="@User" />

Inferir tipos genéricos com base em componentes


ancestrais
A demonstração nesta seção coloca um tipo em cascata explicitamente para TExample .

7 Observação

Esta seção usa os dois componentes ListDisplay na seção Suporte para tipo
genérico em cascata.

Shared/ListGenericTypeItems4.razor :

razor

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Generic Type Items 4</h2>

@ChildContent

@if (ExampleList is not null)


{
<ul style="color:green">
@foreach(var item in ExampleList)
{
<li>@item</li>
}
</ul>
<p>
Type of <code>TExample</code>: @typeof(TExample)
</p>
}

@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }
}

O componente GenericTypeExample4 a seguir com tipos em cascata inferidos fornece


dados diferentes para exibição.

Pages/GenericTypeExample4.razor :

razor

@page "/generic-type-example-4"

<h1>Generic Type Example 4</h1>

<ListGenericTypeItems4 ExampleList="@(new List<string> { "Item 5", "Item 6"


})">
<ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })"
/>
<ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })"
/>
</ListGenericTypeItems4>

<ListGenericTypeItems4 ExampleList="@(new List<int> { 7, 8, 9 })">


<ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" />
<ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" />
</ListGenericTypeItems4>

O componente GenericTypeExample5 a seguir com tipos em cascata inferidos fornece os


mesmos dados para exibição. O exemplo a seguir atribui diretamente os dados aos
componentes.

Pages/GenericTypeExample5.razor :

razor

@page "/generic-type-example-5"

<h1>Generic Type Example 5</h1>

<ListGenericTypeItems4 ExampleList="@stringData">
<ListDisplay1 ExampleList="@stringData" />
<ListDisplay2 ExampleList="@stringData" />
</ListGenericTypeItems4>

<ListGenericTypeItems4 ExampleList="@integerData">
<ListDisplay1 ExampleList="@integerData" />
<ListDisplay2 ExampleList="@integerData" />
</ListGenericTypeItems4>

@code {
private List<string> stringData = new() { "Item 1", "Item 2" };
private List<int> integerData = new() { 1, 2, 3 };
}

Renderizar componentes Razor de JavaScript


Os componentes Razor podem ser renderizados dinamicamente do JavaScript (JS) para
aplicativos JS existentes.

Para renderizar um componente Razor de JS, registre-o como um componente raiz para
renderização de JS e atribua um identificador ao componente:

Em um aplicativo Blazor Server, modifique a chamada paraAddServerSideBlazor


em Program.cs :

C#

builder.Services.AddServerSideBlazor(options =>
{
options.RootComponents.RegisterForJavaScript<Counter>(identifier:
"counter");
});

7 Observação

O exemplo de código anterior requer um namespace para os componentes


do aplicativo (por exemplo, using BlazorSample.Pages; ) no arquivo
Program.cs .

Em um aplicativo Blazor WebAssembly, chame RegisterForJavaScript em


RootComponents em Program.cs :

C#
builder.RootComponents.RegisterForJavaScript<Counter>(identifier:
"counter");

7 Observação

O exemplo de código anterior requer um namespace para os componentes


do aplicativo (por exemplo, using BlazorSample.Pages; ) no arquivo
Program.cs .

Carregue Blazor no aplicativo JS ( blazor.server.js ou blazor.webassembly.js ).


Renderize o componente de JS em um elemento de contêiner usando o identificador
registrado, passando parâmetros de componente conforme necessário:

JavaScript

let containerElement = document.getElementById('my-counter');


await Blazor.rootComponents.add(containerElement, 'counter', {
incrementAmount: 10 });

Elementos personalizados de Blazor


O suporte experimental está disponível para a criação de elementos personalizados
usando o pacote NuGet Microsoft.AspNetCore.Components.CustomElements .
Elementos personalizados usam interfaces HTML padrão para implementar elementos
HTML personalizados.

2 Aviso

Os recursos experimentais são fornecidos com a finalidade de explorar a


viabilidade do recurso e podem não ser fornecidos em uma versão estável.

Registre um componente raiz como um elemento personalizado:

Em um aplicativo Blazor Server, modifique a chamada paraAddServerSideBlazor


em Program.cs :

C#

builder.Services.AddServerSideBlazor(options =>
{
options.RootComponents.RegisterAsCustomElement<Counter>("my-
counter");
});

7 Observação

O exemplo de código anterior requer um namespace para os componentes


do aplicativo (por exemplo, using BlazorSample.Pages; ) no arquivo
Program.cs .

Em um aplicativo Blazor WebAssembly, chame RegisterAsCustomElement em


RootComponents em Program.cs :

C#

builder.RootComponents.RegisterAsCustomElement<Counter>("my-counter");

7 Observação

O exemplo de código anterior requer um namespace para os componentes


do aplicativo (por exemplo, using BlazorSample.Pages; ) no arquivo
Program.cs .

Inclua a seguinte marca <script> no HTML do aplicativo antes da marca de script


Blazor:

HTML

<script
src="/_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomEl
ements.js"></script>

Use o elemento personalizado com qualquer estrutura da Web. Por exemplo, o


elemento personalizado de contador anterior é usado em um aplicativo React com a
seguinte marcação:

HTML

<my-counter increment-amount={incrementAmount}></my-counter>

Para ver um exemplo completo de como criar elementos personalizados com Blazor,
consulte o Projeto de exemplo de Elementos Personalizados Blazor .
2 Aviso

O recurso de elementos personalizados atualmente é experimental, sem suporte e


está sujeito a alterações ou a ser removido a qualquer momento. Gostaríamos de
receber comentários sobre como essa abordagem específica atende aos seus
requisitos.

Gerar componentes Angular e React


Gere componentes JavaScript (JS) específicos da estrutura com base em componentes
Razor para estruturas da Web, como Angular ou React. Essa funcionalidade não está
incluída no .NET 6, mas é habilitada pelo novo suporte para renderizar componentes
Razor de JS. O exemplo de geração de componentes JS no GitHub demonstra como
gerar componentes Angular e React de componentes Razor. Consulte o arquivo
README.md do aplicativo de exemplo do GitHub para obter informações adicionais.

2 Aviso

Os recursos de componente Angular e React são experimentais, sem suporte e


estão sujeitos a alterações ou a ser removidos a qualquer momento. Gostaríamos
de receber comentários sobre como essa abordagem específica atende aos seus
requisitos.
Blazor layouts de ASP.NET Core
Artigo • 28/11/2022 • 31 minutos para o fim da leitura

Este artigo explica como criar componentes de layout reutilizáveis para Blazor
aplicativos.

Alguns elementos de aplicativo, como menus, mensagens de direitos autorais e


logotipos da empresa, geralmente fazem parte da apresentação geral do aplicativo.
Colocar uma cópia da marcação desses elementos em todos os componentes de um
aplicativo não é eficiente. Sempre que um desses elementos é atualizado, todos os
componentes que usam o elemento devem ser atualizados. Essa abordagem é
dispendiosa de manter e pode levar a conteúdo inconsistente se uma atualização for
perdida. Os layouts resolvem esses problemas.

Um Blazor layout é um Razor componente que compartilha a marcação com


componentes que fazem referência a ele. Os layouts podem usar associação de dados,
injeção de dependência e outros recursos de componentes.

Componentes de layout

Criar um componente de layout


Para criar um componente de layout:

Crie um Razor componente definido por um Razor modelo ou código C#. Os


componentes de layout baseados em um Razor modelo usam a .razor extensão
de arquivo, assim como os componentes comuns Razor . Como os componentes
de layout são compartilhados entre os componentes de um aplicativo, eles
geralmente são colocados na pasta do Shared aplicativo. No entanto, os layouts
podem ser colocados em qualquer local acessível aos componentes que o usam.
Por exemplo, um layout pode ser colocado na mesma pasta que os componentes
que o usam.
Herda o componente de LayoutComponentBase. O LayoutComponentBase define
uma Body propriedade (RenderFragment tipo) para o conteúdo renderizado
dentro do layout.
Use a Razor sintaxe @Body para especificar o local na marcação de layout em que o
conteúdo é renderizado.

7 Observação
Para obter mais informações sobre RenderFragment, consulte ASP.NET Core Razor
componentes.

O componente a seguir DoctorWhoLayout mostra o Razor modelo de um componente de


layout. O layout herda LayoutComponentBase e define o entre a @Body barra de
navegação ( <nav>...</nav> ) e o rodapé ( <footer>...</footer> ).

Shared/DoctorWhoLayout.razor :

razor

@inherits LayoutComponentBase

<header>
<h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
<a href="masterlist">Master Episode List</a>
<a href="search">Search</a>
<a href="new">Add Episode</a>
</nav>

@Body

<footer>
@TrademarkMessage
</footer>

@code {
public string TrademarkMessage { get; set; } =
"Doctor Who is a registered trademark of the BBC. " +
"https://www.doctorwho.tv/";
}

componente MainLayout
Em um aplicativo criado a partir de um Blazor modelo de projeto, o MainLayout
componente é o layout padrão do aplicativo. BlazorO layout do adota o Flexbox layout
model (MDN documentation) (especificação W3C ).

Shared/MainLayout.razor :

razor

@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>

<main>
<div class="top-row px-4">
<a href="http://blazor.net" target="_blank" class="ml-md-
auto">About</a>
</div>

<div class="content px-4">


@Body
</div>
</main>
</div>

BlazorO recurso de isolamento CSS aplica estilos CSS isolados ao MainLayout


componente. Por convenção, os estilos são fornecidos pela folha de estilos que
acompanha o mesmo nome, Shared/MainLayout.razor.css . A implementação da
estrutura ASP.NET Core da folha de estilos está disponível para inspeção no ASP.NET
Core fonte de referência ( dotnet/aspnetcore repositório GitHub):

Blazor Server MainLayout.razor.css


Blazor WebAssembly MainLayout.razor.css

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Aplicar um layout

Aplicar um layout a um componente


Use a @layoutRazor diretiva para aplicar um layout a um componente roteável Razor
que tenha uma @page diretiva . O compilador converte @layout em um LayoutAttribute
e aplica o atributo à classe de componente.
O conteúdo do componente a seguir Episodes é inserido DoctorWhoLayout no na
posição de @Body .

Pages/Episodes.razor :

razor

@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
<li>
<a href="https://www.bbc.co.uk/programmes/p00vfknq">
<em>The Ribos Operation</em>
</a>
</li>
<li>
<a href="https://www.bbc.co.uk/programmes/p00vfdsb">
<em>The Sun Makers</em>
</a>
</li>
<li>
<a href="https://www.bbc.co.uk/programmes/p00vhc26">
<em>Nightmare of Eden</em>
</a>
</li>
</ul>

A marcação HTML renderizada a seguir é produzida pelo componente e Episodes


anterior DoctorWhoLayout . A marcação desnecessária não aparece para se concentrar no
conteúdo fornecido pelos dois componentes envolvidos:

O título doctor who™ episode database ( <h1>...</h1> ) no cabeçalho


( <header>...</header> ), barra de navegação ( <nav>...</nav> ) e elemento de
informações de marca registrada ( <div>...</div> ) no rodapé ( <footer>...
</footer> ) vêm do DoctorWhoLayout componente.

O título episódios ( <h2>...</h2> ) e a lista de episódios ( <ul>...</ul> ) vêm do


Episodes componente.

HTML

<body>
<div id="app">
<header>
<h1>Doctor Who&trade; Episode Database</h1>
</header>
<nav>
<a href="main-list">Main Episode List</a>
<a href="search">Search</a>
<a href="new">Add Episode</a>
</nav>

<h2>Episodes</h2>

<ul>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>

<footer>
Doctor Who is a registered trademark of the BBC.
https://www.doctorwho.tv/
</footer>
</div>
</body>

Especificar o layout diretamente em um componente substitui um layout padrão:

Definido por uma @layout diretiva importada de um _Imports componente


( _Imports.razor ), conforme descrito na seção Aplicar um layout a seguir a uma
pasta de componentes .
Defina como o layout padrão do aplicativo, conforme descrito na seção Aplicar um
layout padrão a um aplicativo mais adiante neste artigo.

Aplicar um layout a uma pasta de componentes


Opcionalmente, cada pasta de um aplicativo pode conter um arquivo de modelo
chamado _Imports.razor . O compilador inclui as diretivas especificadas no arquivo de
importações em todos os Razor modelos na mesma pasta e recursivamente em todas as
suas subpastas. Portanto, um _Imports.razor arquivo que @layout DoctorWhoLayout
contém garante que todos os componentes em uma pasta usem o DoctorWhoLayout
componente . Não é necessário adicionar @layout DoctorWhoLayout repetidamente a
todos os Razor componentes ( .razor ) dentro da pasta e das subpastas.

_Imports.razor :

razor

@layout DoctorWhoLayout
...
O _Imports.razor arquivo é semelhante ao arquivo _ViewImports.cshtml para Razor
exibições e páginas , mas aplicado especificamente aos Razor arquivos de componente.

Especificar um layout em _Imports.razor substitui um layout especificado como o


layout de aplicativo padrão do roteador, que é descrito na seção a seguir.

2 Aviso

Não adicione uma Razor @layout diretiva ao arquivo raiz _Imports.razor , o que
resulta em um loop infinito de layouts. Para controlar o layout do aplicativo padrão,
especifique o layout no Router componente . Para obter mais informações,
consulte a seção Aplicar um layout padrão a um aplicativo a seguir.

7 Observação

A @layoutRazor diretiva aplica apenas um layout a componentes roteáveis Razor


com uma @page diretiva .

Aplicar um layout padrão a um aplicativo


Especifique o layout do aplicativo padrão no App componente do Router componente.
O exemplo a seguir de um aplicativo baseado em um Blazor modelo de projeto define o
layout padrão para o MainLayout componente.

App.razor :

razor

<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<p>Sorry, there's nothing at this address.</p>
</NotFound>
</Router>

Para obter mais informações sobre o Router componente, consulte ASP.NET Core Blazor
roteamento e navegação.
Especificar o layout como um layout padrão no Router componente é uma prática útil
porque você pode substituir o layout por componente ou por pasta, conforme descrito
nas seções anteriores deste artigo. É recomendável usar o Router componente para
definir o layout padrão do aplicativo, pois ele é a abordagem mais geral e flexível para
usar layouts.

Aplicar um layout ao conteúdo arbitrário ( LayoutView


componente)
Para definir um layout para conteúdo de modelo arbitrário Razor , especifique o layout
com um LayoutView componente. Você pode usar um LayoutView em qualquer Razor
componente. O exemplo a seguir define um componente de layout chamado
ErrorLayout para o MainLayout modelo do NotFound componente ( <NotFound>...
</NotFound> ).

App.razor :

razor

<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(ErrorLayout)">
<h1>Page not found</h1>
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

Layouts aninhados
Um componente pode referenciar um layout que, por sua vez, faz referência a outro
layout. Por exemplo, layouts aninhados são usados para criar estruturas de menu de
vários níveis.

O exemplo a seguir mostra como usar layouts aninhados. O Episodes componente


mostrado na seção Aplicar um layout a um componente é o componente a ser exibido.
O componente faz referência ao DoctorWhoLayout componente .
O componente a seguir DoctorWhoLayout é uma versão modificada do exemplo
mostrado anteriormente neste artigo. Os elementos de cabeçalho e rodapé são
removidos e o layout faz referência a outro layout, ProductionsLayout . O Episodes
componente é renderizado quando @Body aparece no DoctorWhoLayout .

Shared/DoctorWhoLayout.razor :

razor

@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
<a href="episode-masterlist">Master Episode List</a>
<a href="episode-search">Search</a>
<a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
@TrademarkMessage
</div>

@code {
public string TrademarkMessage { get; set; } =
"Doctor Who is a registered trademark of the BBC. " +
"https://www.doctorwho.tv/";
}

O ProductionsLayout componente contém os elementos de layout de nível superior, em


que os elementos de cabeçalho ( <header>...</header> ) e rodapé ( <footer>...
</footer> ) agora residem. O DoctorWhoLayout com o Episodes componente é
renderizado onde @Body aparece.

Shared/ProductionsLayout.razor :

razor

@inherits LayoutComponentBase

<header>
<h1>Productions</h1>
</header>

<nav>
<a href="master-production-list">Master Production List</a>
<a href="production-search">Search</a>
<a href="new-production">Add Production</a>
</nav>

@Body

<footer>
Footer of Productions Layout
</footer>

A marcação HTML renderizada a seguir é produzida pelo layout aninhado anterior. A


marcação desnecessária não aparece para se concentrar no conteúdo aninhado
fornecido pelos três componentes envolvidos:

Os elementos cabeçalho ( <header>...</header> ), barra de navegação de produção


( <nav>...</nav> ) e rodapé ( <footer>...</footer> ) e seu conteúdo vêm do
ProductionsLayout componente.
O título banco de dados doctor who™ episode ( <h1>...</h1> ), barra de
navegação de episódio ( <nav>...</nav> ) e elemento de informações de marca
registrada ( <div>...</div> ) vêm do DoctorWhoLayout componente.
O título episódios ( <h2>...</h2> ) e a lista de episódios ( <ul>...</ul> ) vêm do
Episodes componente.

HTML

<body>
<div id="app">
<header>
<h1>Productions</h1>
</header>

<nav>
<a href="main-production-list">Main Production List</a>
<a href="production-search">Search</a>
<a href="new-production">Add Production</a>
</nav>

<h1>Doctor Who&trade; Episode Database</h1>

<nav>
<a href="episode-main-list">Main Episode List</a>
<a href="episode-search">Search</a>
<a href="new-episode">Add Episode</a>
</nav>

<h2>Episodes</h2>

<ul>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>

<div>
Doctor Who is a registered trademark of the BBC.
https://www.doctorwho.tv/
</div>

<footer>
Footer of Productions Layout
</footer>
</div>
</body>

Compartilhar um Razor layout de Páginas com


componentes integrados
Quando componentes roteáveis são integrados a um Razor aplicativo Pages, o layout
compartilhado do aplicativo pode ser usado com os componentes. Para obter mais
informações, consulte Pré-gerar e integrar componentes ASP.NET CoreRazor.

Recursos adicionais
Layout no ASP.NET Core
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Controlar <head> o conteúdo em
aplicativos ASP.NET Core Blazor
Artigo • 28/11/2022 • 3 minutos para o fim da leitura

Razor os componentes podem modificar o conteúdo do elemento HTML <head> de


uma página, incluindo a definição do título da página ( <title> elemento) e a
modificação de metadados ( <meta> elementos).

Controlar <head> o conteúdo em um Razor


componente
Especifique o título da página com o PageTitle componente , que permite renderizar um
elemento HTML <title> em um HeadOutlet componente.

Especifique <head> o conteúdo do elemento com o HeadContent componente , que


fornece conteúdo a um HeadOutlet componente.

O exemplo a seguir define o título e a descrição da página usando Razor.

Pages/ControlHeadContent.razor :

razor

@page "/control-head-content"

<h1>Control <head> content</h1>

<p>
Title: @title
</p>

<p>
Description: @description
</p>

<PageTitle>@title</PageTitle>

<HeadContent>
<meta name="description" content="@description">
</HeadContent>

@code {
private string description = "Description set by component";
private string title = "Title set by component";
}
componente HeadOutlet
O HeadOutlet componente renderiza o conteúdo fornecido pelos PageTitle
componentes e HeadContent .

Em um aplicativo criado com base no Blazor WebAssembly modelo de projeto, o


HeadOutlet componente é adicionado à RootComponents coleção do
WebAssemblyHostBuilder em Program.cs :

C#

builder.RootComponents.Add<HeadOutlet>("head::after");

Quando o ::after pseudo seletor é especificado, o conteúdo do componente raiz é


acrescentado ao conteúdo de cabeçalho existente em vez de substituir o conteúdo. Isso
permite que o aplicativo mantenha o conteúdo de cabeçalho estático no
wwwroot/index.html sem precisar repetir o conteúdo nos componentes do Razor

aplicativo.

Em Blazor Server aplicativos criados com base no Blazor Server modelo de projeto, um
Auxiliar de Marca de Componente renderiza <head> o conteúdo do HeadOutlet
componente em Pages/_Host.cshtml :

CSHTML

<head>
...
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>

Título da página não encontrado


Em Blazor aplicativos criados a partir de modelos de projeto, o NotFound modelo de
Blazor componente no App componente ( App.razor ) define o título da página como
Not found .

App.razor :

razor

<PageTitle>Not found</PageTitle>
Recursos adicionais
Cabeçalhos de controle no código C# na inicialização
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)

Documentação do Mozilla MDN Web Docs:

O que tem na cabeça? Metadados em HTML


<head>: o elemento Metadados do Documento (Cabeçalho)
<title>: o elemento Título do Documento
<meta>: o elemento de metadados
Blazor ASP.NET Core valores e
parâmetros em cascata
Artigo • 28/11/2022 • 21 minutos para o fim da leitura

Este artigo explica como fluir dados de um componente ancestral Razor para
componentes descendentes.

Valores e parâmetros em cascata fornecem uma maneira conveniente de fluir dados para
baixo de uma hierarquia de componentes de um componente ancestral para qualquer
número de componentes descendentes. Ao contrário dos parâmetros component,
valores e parâmetros em cascata não exigem uma atribuição de atributo para cada
componente descendente em que os dados são consumidos. Os valores e parâmetros
em cascata também permitem que os componentes coordenem uns com os outros em
uma hierarquia de componentes.

componente CascadingValue
Um componente ancestral fornece um valor em cascata usando o Blazor componente
da CascadingValue estrutura, que encapsula uma subárvore de uma hierarquia de
componentes e fornece um único valor para todos os componentes dentro de sua
subárvore.

O exemplo a seguir demonstra o fluxo de informações de tema na hierarquia de


componentes de um componente de layout para fornecer uma classe de estilo CSS a
botões em componentes filho.

A classe C# a seguir ThemeInfo é colocada em uma pasta chamada UIThemeClasses e


especifica as informações do tema.

7 Observação

Para os exemplos nesta seção, o namespace do aplicativo é BlazorSample . Ao


experimentar o código em seu próprio aplicativo de exemplo, altere o namespace
do aplicativo para o namespace do aplicativo de exemplo.

UIThemeClasses/ThemeInfo.cs :

C#
namespace BlazorSample.UIThemeClasses
{
public class ThemeInfo
{
public string? ButtonClass { get; set; }
}
}

O componente de layout a seguir especifica informações de tema ( ThemeInfo ) como um


valor em cascata para todos os componentes que compõem o corpo do layout da Body
propriedade. ButtonClass é atribuído um valor de btn-success , que é um estilo de
botão Bootstrap. Qualquer componente descendente na hierarquia de componentes
pode usar a ButtonClass propriedade por meio do ThemeInfo valor em cascata.

Shared/MainLayout.razor :

razor

@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
<div class="sidebar">
<NavMenu />
</div>

<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/"
target="_blank">About</a>
</div>

<CascadingValue Value="@theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
</main>
</div>

@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}

Atributo [CascadingParameter]
Para usar valores em cascata, os componentes descendentes declaram parâmetros em
cascata usando o [CascadingParameter] atributo . Os valores em cascata estão
associados a parâmetros em cascata por tipo. Vários valores em cascata do mesmo tipo
são abordados na seção Cascata de vários valores posteriormente neste artigo.

O componente a seguir associa o ThemeInfo valor em cascata a um parâmetro em


cascata, opcionalmente usando o mesmo nome de ThemeInfo . O parâmetro é usado
para definir a classe CSS para o Increment Counter (Themed) botão.

Pages/ThemedCounter.razor :

razor

@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

<p>Current count: @currentCount</p>

<p>
<button @onclick="IncrementCount">
Increment Counter (Unthemed)
</button>
</p>

<p>
<button
class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass :
string.Empty)"
@onclick="IncrementCount">
Increment Counter (Themed)
</button>
</p>

@code {
private int currentCount = 0;

[CascadingParameter]
protected ThemeInfo? ThemeInfo { get; set; }

private void IncrementCount()


{
currentCount++;
}
}

Semelhante a um parâmetro de componente regular, os componentes que aceitam um


parâmetro em cascata são gerados novamente quando o valor em cascata é alterado.
Por exemplo, a configuração de uma instância de tema diferente faz com que o
ThemedCounter componente da seção de CascadingValue componente seja rerender:

Shared/MainLayout.razor :

razor

<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/"
target="_blank">About</a>
</div>

<CascadingValue Value="@theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
<button @onclick="ChangeToDarkTheme">Dark mode</button>
</main>

@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };

private void ChangeToDarkTheme()


{
theme = new() { ButtonClass = "btn-darkmode-success" };
}
}

CascadingValue<TValue>.IsFixed pode ser usado para indicar que um parâmetro em


cascata não é alterado após a inicialização.

Valores múltiplos em cascata


Para colocar em cascata vários valores do mesmo tipo na mesma subárvore, forneça
uma cadeia de caracteres exclusiva Name para cada CascadingValue componente e seus
atributos correspondentes[CascadingParameter].

No exemplo a seguir, dois CascadingValue componentes exibem instâncias diferentes


de CascadingType :

razor

<CascadingValue Value="@parentCascadeParameter1" Name="CascadeParam1">


<CascadingValue Value="@ParentCascadeParameter2" Name="CascadeParam2">
...
</CascadingValue>
</CascadingValue>

@code {
private CascadingType? parentCascadeParameter1;

[Parameter]
public CascadingType? ParentCascadeParameter2 { get; set; }
}

Em um componente descendente, os parâmetros em cascata recebem seus valores em


cascata do componente ancestral por Name:

razor

@code {
[CascadingParameter(Name = "CascadeParam1")]
protected CascadingType? ChildCascadeParameter1 { get; set; }

[CascadingParameter(Name = "CascadeParam2")]
protected CascadingType? ChildCascadeParameter2 { get; set; }
}

Passar dados em uma hierarquia de


componentes
Os parâmetros em cascata também permitem que os componentes passem dados por
uma hierarquia de componentes. Considere o exemplo de conjunto de guias da
interface do usuário a seguir, em que um componente de conjunto de guias mantém
uma série de guias individuais.

7 Observação

Para os exemplos nesta seção, o namespace do aplicativo é BlazorSample . Ao


experimentar o código em seu próprio aplicativo de exemplo, altere o namespace
para o namespace do aplicativo de exemplo.

Crie uma ITab interface que as guias implementem em uma pasta chamada
UIInterfaces .

UIInterfaces/ITab.cs :

C#
using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces
{
public interface ITab
{
RenderFragment ChildContent { get; }
}
}

7 Observação

Para obter mais informações sobre RenderFragment, consulte ASP.NET Core Razor
componentes.

O componente a seguir TabSet mantém um conjunto de guias. Os componentes do


conjunto de Tab guias, que são criados posteriormente nesta seção, fornecem os itens
de lista ( <li>...</li> ) para a lista ( <ul>...</ul> ).

Os componentes filho Tab não são explicitamente passados como parâmetros para o
TabSet . Em vez disso, os componentes filho Tab fazem parte do conteúdo filho do

TabSet . No entanto, o TabSet ainda precisa de uma referência a cada Tab componente
para que ele possa renderizar os cabeçalhos e a guia ativa. Para habilitar essa
coordenação sem exigir código adicional, o TabSet componente pode fornecer-se como
um valor em cascata que, em seguida, é coletado pelos componentes descendentes Tab
.

Shared/TabSet.razor :

razor

@using BlazorSample.UIInterfaces

<!-- Display the tab headers -->

<CascadingValue Value="this">
<ul class="nav nav-tabs">
@ChildContent
</ul>
</CascadingValue>

<!-- Display body for only the active tab -->

<div class="nav-tabs-body p-4">


@ActiveTab?.ChildContent
</div>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }

public ITab? ActiveTab { get; private set; }

public void AddTab(ITab tab)


{
if (ActiveTab is null)
{
SetActiveTab(tab);
}
}

public void SetActiveTab(ITab tab)


{
if (ActiveTab != tab)
{
ActiveTab = tab;
StateHasChanged();
}
}
}

Os componentes descendentes Tab capturam o que contém TabSet como um


parâmetro em cascata. Os Tab componentes se adicionam à TabSet coordenada e para
definir a guia ativa.

Shared/Tab.razor :

razor

@using BlazorSample.UIInterfaces
@implements ITab

<li>
<a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
@Title
</a>
</li>

@code {
[CascadingParameter]
public TabSet? ContainerTabSet { get; set; }

[Parameter]
public string? Title { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }
private string? TitleCssClass =>
ContainerTabSet?.ActiveTab == this ? "active" : null;

protected override void OnInitialized()


{
ContainerTabSet?.AddTab(this);
}

private void ActivateTab()


{
ContainerTabSet?.SetActiveTab(this);
}
}

O componente a seguir ExampleTabSet usa o TabSet componente , que contém três Tab
componentes.

Pages/ExampleTabSet.razor :

razor

@page "/example-tab-set"

<TabSet>
<Tab Title="First tab">
<h4>Greetings from the first tab!</h4>

<label>
<input type="checkbox" @bind="showThirdTab" />
Toggle third tab
</label>
</Tab>

<Tab Title="Second tab">


<h4>Hello from the second tab!</h4>
</Tab>

@if (showThirdTab)
{
<Tab Title="Third tab">
<h4>Welcome to the disappearing third tab!</h4>
<p>Toggle this tab from the first tab.</p>
</Tab>
}
</TabSet>

@code {
private bool showThirdTab;
}
Blazor associação de dados ASP.NET
Core
Artigo • 16/01/2023 • 56 minutos para o fim da leitura

Este artigo explica os recursos de associação de dados para Razor componentes e


elementos do DOM (Modelo de Objeto de Documento) em Blazor aplicativos.

Razor os componentes fornecem recursos de associação de dados com o @bindRazor


atributo de diretiva com um valor de campo, propriedade ou Razor expressão.

O exemplo a seguir associa:

Um <input> valor de elemento para o campo C# inputValue .


Um segundo <input> valor de elemento para a propriedade C# InputValue .

Quando um <input> elemento perde o foco, seu campo ou propriedade associada é


atualizado.

Pages/Bind.razor :

razor

@page "/bind"

<p>
<input @bind="inputValue" />
</p>

<p>
<input @bind="InputValue" />
</p>

<ul>
<li><code>inputValue</code>: @inputValue</li>
<li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
private string? inputValue;

private string? InputValue { get; set; }


}

A caixa de texto é atualizada na interface do usuário somente quando o componente é


renderizado, não em resposta à alteração do valor do campo ou da propriedade. Como
os componentes se renderizam após a execução do código do manipulador de eventos,
as atualizações de campo e propriedade geralmente são refletidas na interface do
usuário imediatamente após um manipulador de eventos ser disparado.

Como uma demonstração de como a associação de dados compõe em HTML, o


exemplo a seguir associa a InputValue propriedade aos atributos e onchange do value
segundo <input> elemento (change ). O segundo <input> elemento no exemplo a
seguir é uma demonstração de conceito e não se destina a sugerir como você deve
associar dados em Razor componentes.

Pages/BindTheory.razor :

razor

@page "/bind-theory"

<p>
<label>
Normal Blazor binding:
<input @bind="InputValue" />
</label>
</p>

<p>
<label>
Demonstration of equivalent HTML binding:
<input value="@InputValue"
@onchange="@((ChangeEventArgs __e) => InputValue =
__e?.Value?.ToString())" />
</label>
</p>

<p>
<code>InputValue</code>: @InputValue
</p>

@code {
private string? InputValue { get; set; }
}

Quando o BindTheory componente é renderizado, o value do elemento de


demonstração <input> HTML vem da InputValue propriedade . Quando o usuário
insere um valor na caixa de texto e altera o foco do elemento, o onchange evento é
acionado e a InputValue propriedade é definida como o valor alterado. Na realidade, a
execução de código é mais complexa porque @bind manipula casos em que conversões
de tipo são executadas. Em geral, @bind associa o valor atual de uma expressão a um
value atributo e manipula as alterações usando o manipulador registrado.
Associe uma propriedade ou campo em outros eventos do DOM (Document Object
Model), incluindo um @bind:event="{EVENT}" atributo com um evento DOM para o
{EVENT} espaço reservado. O exemplo a seguir associa a InputValue propriedade ao

<input> valor do elemento quando o evento do oninput elemento (input )é


disparado. Ao contrário do onchange evento (change ), que é acionado quando o
elemento perde o foco, oninput (input ) é acionado quando o valor da caixa de texto é
alterado.

Page/BindEvent.razor :

razor

@page "/bind-event"

<p>
<input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
<code>InputValue</code>: @InputValue
</p>

@code {
private string? InputValue { get; set; }
}

Para executar a lógica assíncrona após a associação, use @bind:after="{EVENT}" com um


evento DOM para o {EVENT} espaço reservado. Um método C# atribuído não é
executado até que o valor associado seja atribuído de forma síncrona.

Não há suporte para o uso de um parâmetro de retorno de chamada de evento


( [Parameter] public EventCallback<string> ValueChanged { get; set; } ) com
@bind:after . Em vez disso, passe um método que retorna um Action ou Task para
@bind:after .

) Importante

O @bind:after recurso está recebendo mais atualizações no momento. Para


aproveitar as atualizações mais recentes, confirme se você instalou o SDK mais
recente . As atualizações do Visual Studio para dar suporte ao recurso estão
planejadas para a versão do Visual Studio de meados de fevereiro.

Para obter mais informações, consulte Blazor@bind:after não funcionando na


versão RTM do .NET 7 (dotnet/aspnetcore #44957) .
No exemplo a seguir:

O <input> do value elemento está associado ao valor de searchText forma


síncrona.
Após cada pressionamento de tecla ( onchange evento) no campo, o PerformSearch
método é executado de forma assíncrona.
PerformSearch chama um serviço com um método assíncrono ( FetchAsync ) para

retornar os resultados da pesquisa.

razor

@inject ISearchService SearchService

<input @bind="searchText" @bind:after="PerformSearch" />

@code {
private string? searchText;
private string[]? searchResult;

private async Task PerformSearch()


{
searchResult = await SearchService.FetchAsync(searchText);
}
}

Mais exemplos

Pages/BindAfter.razor :

razor

@page "/bind-after"
@using Microsoft.AspNetCore.Components.Forms

<h1>Bind After Examples</h1>

<h2>Elements</h2>

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind="text" @bind:after="After" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<h2>Components</h2>

<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value="text" @bind-Value:after="After" />


<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />

@code {
private string text = "";

private void After() {}


private Task AfterAsync() { return Task.CompletedTask; }
}

Para obter mais informações sobre o InputText componente, consulte ASP.NET Core
Blazor formulários e componentes de entrada.

Os componentes dão suporte à associação de dados bidirecional definindo um par de


parâmetros:

@bind:get : especifica o valor a ser associado.

@bind:set : especifica um retorno de chamada para quando o valor é alterado.

) Importante

Os @bind:get recursos e @bind:set estão recebendo mais atualizações no


momento. Para aproveitar as atualizações mais recentes, confirme se você instalou
o SDK mais recente. As atualizações do Visual Studio para dar suporte ao recurso
estão planejadas para a versão do Visual Studio de meados de fevereiro.

Para obter mais informações, consulte Blazor@bind:after não funcionando na


versão RTM do .NET 7 (dotnet/aspnetcore #44957) .

Os @bind:get modificadores e @bind:set são sempre usados juntos.

Não há suporte para o uso de um parâmetro de retorno de chamada de evento com


@bind:set ( [Parameter] public EventCallback<string> ValueChanged { get; set; } ). Em
vez disso, passe um método que retorna um Action ou Task para @bind:set .

Exemplos

Pages/BindGetSet.razor :

razor

@page "/bind-get-set"
@using Microsoft.AspNetCore.Components.Forms

<h1>Bind Get Set Examples</h1>

<h2>Elements</h2>
<input type="text" @bind:get="text" @bind:set="(value) => { text = value; }"
/>
<input type="text" @bind:get="text" @bind:set="Set" />
<input type="text" @bind:get="text" @bind:set="SetAsync" />

<h2>Components</h2>

<InputText @bind-Value:get="text" @bind-Value:set="(value) => { text =


value; }" />
<InputText @bind-Value:get="text" @bind-Value:set="Set" />
<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

@code {
private string text = "";

private void Set(string value)


{
text = value;
}

private Task SetAsync(string value)


{
text = value;
return Task.CompletedTask;
}
}

Para obter mais informações sobre o InputText componente, consulte ASP.NET Core
Blazor formulários e componentes de entrada.

Razor A associação de atributo diferencia maiúsculas de minúsculas:

@bind , @bind:event e @bind:after são válidos.

@Bind / @bind:Event / @bind:aftEr (letras maiúsculas) ou

@BIND / @BIND:AFTER / @BIND:EVENT (todas as letras maiúsculas) são inválidas.

Usar @bind:get / @bind:set modificadores e


evitar manipuladores de eventos para
associação de dados bidirecional
A associação de dados bidirecional não é possível implementar com um manipulador de
eventos. Use @bind:get / @bind:set modificadores para associação de dados
bidirecional.

❌ Considere a seguinte abordagem disfuncional para associação de dados


bidirecional usando um manipulador de eventos:
razor

<p>
<input value="@inputValue" @oninput="OnInput" />
</p>

<p>
<code>inputValue</code>: @inputValue
</p>

@code {
private string? inputValue;

private void OnInput(ChangeEventArgs args)


{
var newValue = args.Value?.ToString() ?? string.Empty;

inputValue = newValue.Length > 4 ? "Long!" : inputValue = newValue;


}
}

O OnInput manipulador de eventos atualiza o valor de para Long! depois que


inputValue um quarto caractere é fornecido. No entanto, o usuário pode continuar

adicionando caracteres ao valor do elemento na interface do usuário. O valor de


inputValue não está associado de volta ao valor do elemento com cada
pressionamento de tecla. O exemplo anterior só é capaz de associar dados
unidirecionais.

O motivo para esse comportamento é que Blazor não está ciente de que seu código
pretende modificar o valor de no manipulador de inputValue eventos. Blazor não tenta
forçar valores de elemento DOM (Document Object Model) e valores de variáveis
.NET a corresponder, a menos que estejam associados à @bind sintaxe. Em versões
anteriores do Blazor, a associação de dados bidirecional é implementada associando o
elemento a uma propriedade e controlando o valor da propriedade com seu setter. No
ASP.NET Core 7.0 ou posterior, @bind:get / @bind:set a sintaxe do modificador é usada
para implementar a associação de dados bidirecional, como demonstra o próximo
exemplo.

✔️Considere a seguinte abordagem correta usando @bind:get / @bind:set para


associação de dados bidirecional:

razor

<p>
<input @bind:event="oninput" @bind:get="inputValue" @bind:set="OnInput"
/>
</p>
<p>
<code>inputValue</code>: @inputValue
</p>

@code {
private string? inputValue;

private void OnInput(string value)


{
var newValue = value ?? string.Empty;

inputValue = newValue.Length > 4 ? "Long!" : inputValue = newValue;


}
}

O uso @bind:get / @bind:set de modificadores controla o valor subjacente de


inputValue via @bind:set e associa o valor de inputValue ao valor do elemento por

meio @bind:get de . O exemplo anterior demonstra a abordagem correta para


implementar a associação de dados bidirecional.

Associação a uma propriedade com C# get e


set acessadores
Os acessadores e set C# get podem ser usados para criar um comportamento de
formato de associação personalizado, como demonstra o componente a
DecimalBinding seguir. O componente associa um decimal positivo ou negativo a até

três casas decimais a um <input> elemento por meio de uma string propriedade
( DecimalValue ).

Pages/DecimalBinding.razor :

razor

@page "/decimal-binding"
@using System.Globalization

<p>
<label>
Decimal value (±0.000 format):
<input @bind="DecimalValue" />
</label>
</p>

<p>
<code>decimalValue</code>: @decimalValue
</p>
@code {
private decimal decimalValue = 1.1M;
private NumberStyles style =
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-
US");

private string DecimalValue


{
get => decimalValue.ToString("0.000", culture);
set
{
if (Decimal.TryParse(value, style, culture, out var number))
{
decimalValue = Math.Round(number, 3);
}
}
}
}

7 Observação

Para associação de dados bidirecional no ASP.NET Core 7.0 ou posterior,


recomendamos usar @bind:get / @bind:set modificadores. Para obter mais
informações, consulte na @bind:get / @bind:set seção de abertura deste artigo.

Seleção de várias opções com <select>


elementos
A associação dá multiple suporte à seleção de opção com <select> elementos. O
@onchange evento fornece uma matriz dos elementos selecionados por meio de
argumentos de evento (ChangeEventArgs). O valor deve ser associado a um tipo de
matriz.

Pages/BindMultipleInput.razor :

razor

@page "/bind-multiple-input"

<h1>Bind Multiple <code>input</code>Example</h1>

<p>
<label>
Select one or more cars:
<select @onchange="SelectedCarsChanged" multiple>
<option value="audi">Audi</option>
<option value="jeep">Jeep</option>
<option value="opel">Opel</option>
<option value="saab">Saab</option>
<option value="volvo">Volvo</option>
</select>
</label>
</p>

<p>
Selected Cars: @string.Join(", ", SelectedCars)
</p>

<p>
<label>
Select one or more cities:
<select @bind="SelectedCities" multiple>
<option value="bal">Baltimore</option>
<option value="la">Los Angeles</option>
<option value="pdx">Portland</option>
<option value="sf">San Francisco</option>
<option value="sea">Seattle</option>
</select>
</label>
</p>

<span>
Selected Cities: @string.Join(", ", SelectedCities)
</span>

@code {
public string[] SelectedCars { get; set; } = new string[] { };
public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };

private void SelectedCarsChanged(ChangeEventArgs e)


{
if (e.Value is not null)
{
SelectedCars = (string[])e.Value;
}
}
}

Para obter informações sobre como cadeias de caracteres e null valores vazios são
tratados na associação de dados, consulte a seção Opções de elemento Binding
<select> para valores de objeto null C# .

Opções <select> de elemento de associação a


valores de objeto null C#
Não há nenhuma maneira sensata de representar um <select> valor de opção de
elemento como um valor de objeto null C#, porque:

Atributos HTML não podem ter null valores. O equivalente mais próximo a null
em HTML é a ausência do atributo HTML value do <option> elemento .
Ao selecionar um <option> sem value atributo, o navegador trata o valor como o
conteúdo de texto desse <option> elemento.

A Blazor estrutura não tenta suprimir o comportamento padrão porque envolveria:

Criando uma cadeia de soluções alternativas de caso especial na estrutura.


Alterações interruptivas no comportamento da estrutura atual.

O equivalente mais plausível null em HTML é uma cadeia de caracteres value vazia. A
Blazor estrutura lida com conversões de cadeia de caracteres null vazias para
associação bidirecional a um valor de um <select> .

Valores nãoparáveis
Quando um usuário fornece um valor indisplicável para um elemento de entrada de
dados, o valor nãoparável é revertido automaticamente para seu valor anterior quando
o evento bind é disparado.

Considere o componente a seguir, em que um <input> elemento está associado a um


int tipo com um valor inicial de 123 .

Pages/UnparsableValues.razor :

razor

@page "/unparseable-values"

<p>
<input @bind="inputValue" />
</p>

<p>
<code>inputValue</code>: @inputValue
</p>

@code {
private int inputValue = 123;
}
Por padrão, a associação se aplica ao evento do onchange elemento. Se o usuário
atualizar o valor da entrada da caixa de texto para 123.45 e alterar o foco, o valor do
elemento será revertido para 123 quando onchange for acionado. Quando o valor
123.45 é rejeitado em favor do valor original de 123 , o usuário entende que seu valor
não foi aceito.

Para o oninput evento ( @bind:event="oninput" ), uma reversão de valor ocorre após


qualquer pressionamento de tecla que introduz um valor indisplicável. Ao direcionar o
oninput evento com um int tipo associado, um usuário é impedido de digitar um

caractere de ponto ( . ). Um caractere de ponto ( . ) é imediatamente removido,


portanto, o usuário recebe comentários imediatos de que apenas números inteiros são
permitidos. Há cenários em que reverter o valor no oninput evento não é ideal, como
quando o usuário deve ter permissão para limpar um valor indisplicável <input> . As
alternativas incluem:

Não use o oninput evento . Use o evento padrão onchange , em que um valor
inválido não é revertido até que o elemento perca o foco.
Associe a um tipo que permite valor nulo, como ou e use
@bind:get @bind:set /modificadores (descritos anteriormente neste artigo) ou
associe a uma propriedade com lógica personalizada get e set de acessador para
lidar com entradas inválidas. string int?
Use um componente de validação de formulário, como InputNumber<TValue> ou
InputDate<TValue>. Os componentes de validação de formulário fornecem
suporte interno para gerenciar entradas inválidas. Componentes de validação de
formulário:
Permitir que o usuário forneça entrada inválida e receba erros de validação no
associado EditContext.
Exiba erros de validação na interface do usuário sem interferir com o usuário
inserindo dados adicionais de webform.

Cadeias de caracteres de formato


A associação de dados funciona com uma única DateTime cadeia de caracteres de
formato usando @bind:format="{FORMAT STRING}" , em que o {FORMAT STRING} espaço
reservado é a cadeia de caracteres de formato. Outras expressões de formato, como
formatos de moeda ou número, não estão disponíveis no momento, mas podem ser
adicionadas em uma versão futura.

Pages/DateBinding.razor :
razor

@page "/date-binding"

<p>
<label>
<code>yyyy-MM-dd</code> format:
<input @bind="startDate" @bind:format="yyyy-MM-dd" />
</label>
</p>

<p>
<code>startDate</code>: @startDate
</p>

@code {
private DateTime startDate = new(2020, 1, 1);
}

No código anterior, o <input> tipo de campo do elemento ( type atributo) usa como
padrão text .

Anulável System.DateTime e System.DateTimeOffset têm suporte:

C#

private DateTime? date;


private DateTimeOffset? dateOffset;

Não é recomendável especificar um formato para o date tipo de campo porque Blazor
tem suporte interno para formatar datas. Apesar da recomendação, use apenas o yyyy-
MM-dd formato de data para associação funcionar corretamente se um formato for

fornecido com o date tipo de campo:

razor

<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd">

Associação com parâmetros de componente


Um cenário comum é associar uma propriedade de um componente filho a uma
propriedade em seu componente pai. Esse cenário é chamado de associação encadeada
porque vários níveis de associação ocorrem simultaneamente.
Os parâmetros de componente permitem propriedades de associação de um
componente pai com @bind-{PROPERTY} sintaxe, em que o {PROPERTY} espaço reservado
é a propriedade a ser associada.

Você não pode implementar associações encadeadas com @bind sintaxe no


componente filho. Um manipulador de eventos e um valor devem ser especificados
separadamente para dar suporte à atualização da propriedade no pai do componente
filho.

O componente pai ainda aproveita a @bind sintaxe para configurar a associação de


dados com o componente filho.

O componente a seguir ChildBind tem um Year parâmetro de componente e um


EventCallback<TValue>. Por convenção, o EventCallback<TValue> para o parâmetro
deve ser nomeado como o nome do parâmetro de componente com um sufixo
" Changed ". A sintaxe de nomenclatura é {PARAMETER NAME}Changed , em que o {PARAMETER
NAME} espaço reservado é o nome do parâmetro. No exemplo a seguir, o
EventCallback<TValue> é chamado YearChanged de .

EventCallback.InvokeAsync invoca o delegado associado à associação com o argumento


fornecido e envia uma notificação de evento para a propriedade alterada.

Shared/ChildBind.razor :

razor

<div class="card bg-light mt-3" style="width:18rem ">


<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from
Child</button>
</div>
</div>

@code {
private Random r = new();

[Parameter]
public int Year { get; set; }

[Parameter]
public EventCallback<int> YearChanged { get; set; }

private async Task UpdateYearFromChild()


{
await YearChanged.InvokeAsync(r.Next(1950, 2021));
}
}

Para obter mais informações sobre eventos e EventCallback<TValue>, consulte a seção


EventCallback do artigo ASP.NET Core Blazor tratamento de eventos.

No componente a seguir Parent1 , o year campo está associado ao Year parâmetro do


componente filho. O Year parâmetro é associável porque tem um evento
complementar YearChanged que corresponde ao tipo do Year parâmetro.

Pages/Parent1.razor :

razor

@page "/parent-1"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
private Random r = new();
private int year = 1979;

private void UpdateYear()


{
year = r.Next(1950, 2021);
}
}

A associação de parâmetro de componente também pode disparar @bind:after


eventos. No exemplo a seguir, o YearUpdated método é executado de forma assíncrona
após associar o parâmetro de Year componente.

razor

<ChildBind @bind-Year="year" @bind-Year:after="YearUpdated" />

@code {
...

private async Task YearUpdated()


{
... = await ...;
}
}

Por convenção, uma propriedade pode ser associada a um manipulador de eventos


correspondente, incluindo um @bind-{PROPERTY}:event atributo atribuído ao
manipulador, em que o {PROPERTY} espaço reservado é a propriedade . <ChildBind
@bind-Year="year" /> é equivalente à gravação:

razor

<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />

Em um exemplo mais sofisticado e real, o seguinte PasswordEntry componente:

Define o valor de um <input> elemento como um password campo.


Expõe alterações de uma Password propriedade a um componente pai com um
EventCallback que passa o valor atual do campo do password filho como seu
argumento.
Usa o onclick evento para disparar o ToggleShowPassword método . Para obter
mais informações, consulte ASP.NET Core Blazor manipulação de eventos.

Shared/PasswordEntry.razor :

razor

<div class="card bg-light mt-3" style="width:22rem ">


<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged"
required
type="@(showPassword ? "text" : "password")"
value="@password" />
</label>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>

@code {
private bool showPassword;
private string? password;
[Parameter]
public string? Password { get; set; }

[Parameter]
public EventCallback<string> PasswordChanged { get; set; }

private async Task OnPasswordChanged(ChangeEventArgs e)


{
password = e?.Value?.ToString();

await PasswordChanged.InvokeAsync(password);
}

private void ToggleShowPassword()


{
showPassword = !showPassword;
}
}

O PasswordEntry componente é usado em outro componente, como o exemplo de


componente a seguir PasswordBinding .

Pages/PasswordBinding.razor :

razor

@page "/password-binding"

<h1>Password Binding</h1>

<PasswordEntry @bind-Password="password" />

<p>
<code>password</code>: @password
</p>

@code {
private string password = "Not set";
}

Quando o PasswordBinding componente é renderizado inicialmente, o password valor


de Not set é exibido na interface do usuário. Após a renderização inicial, o valor de
password reflete as alterações feitas no valor do parâmetro de Password componente no

PasswordEntry componente.

7 Observação
O exemplo anterior associa a senha unidirecional do componente filho
PasswordEntry ao componente pai PasswordBinding . A associação bidirecional não
será um requisito nesse cenário se a meta for que o aplicativo tenha um
componente de entrada de senha compartilhada para reutilização em torno do
aplicativo que simplesmente passa a senha para o pai. Para obter uma abordagem
que permita a associação bidirecional sem gravar diretamente no parâmetro do
componente filho, consulte o NestedChild exemplo de componente na seção
Associar em mais de dois componentes deste artigo.

Executar verificações ou interceptar erros no manipulador. O componente revisado


PasswordEntry a seguir fornecerá comentários imediatos ao usuário se um espaço for

usado no valor da senha.

Shared/PasswordEntry.razor :

razor

<div class="card bg-light mt-3" style="width:22rem ">


<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged"
required
type="@(showPassword ? "text" : "password")"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>

@code {
private bool showPassword;
private string? password;
private string? validationMessage;

[Parameter]
public string? Password { get; set; }

[Parameter]
public EventCallback<string> PasswordChanged { get; set; }

private Task OnPasswordChanged(ChangeEventArgs e)


{
password = e?.Value?.ToString();

if (password != null && password.Contains(' '))


{
validationMessage = "Spaces not allowed!";

return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;

return PasswordChanged.InvokeAsync(password);
}
}

private void ToggleShowPassword()


{
showPassword = !showPassword;
}
}

No exemplo a seguir, o PasswordUpdated método é executado de forma assíncrona após


associar o parâmetro de Password componente:

razor

<PasswordEntry @bind-Password="password" @bind-


Password:after="PasswordUpdated" />

Associar entre mais de dois componentes


Você pode associar parâmetros por meio de qualquer número de componentes
aninhados, mas deve respeitar o fluxo unidirecional de dados:

As notificações de alteração fluem para cima na hierarquia.


Novos valores de parâmetro fluem para baixo na hierarquia.

Uma abordagem comum e recomendada é armazenar apenas os dados subjacentes no


componente pai para evitar qualquer confusão sobre qual estado deve ser atualizado,
conforme mostrado no exemplo a seguir.

Pages/Parent2.razor :

razor

@page "/parent-2"
<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
private string parentMessage = "Initial value set in Parent";

private void ChangeValue()


{
parentMessage = $"Set in Parent {DateTime.Now}";
}
}

Shared/NestedChild.razor :

razor

<div class="border rounded m-1 p-1">


<h2>Child Component</h2>

<p>Child Message: <b>@ChildMessage</b></p>

<p>
<button @onclick="ChangeValue">Change from Child</button>
</p>

<NestedGrandchild @bind-GrandchildMessage="BoundValue" />


</div>

@code {
[Parameter]
public string? ChildMessage { get; set; }

[Parameter]
public EventCallback<string> ChildMessageChanged { get; set; }

private string BoundValue


{
get => ChildMessage ?? string.Empty;
set => ChildMessageChanged.InvokeAsync(value);
}

private async Task ChangeValue()


{
await ChildMessageChanged.InvokeAsync(
$"Set in Child {DateTime.Now}");
}
}

2 Aviso

Em geral, evite criar componentes que gravam diretamente em seus próprios


parâmetros de componente. O componente anterior NestedChild usa uma
BoundValue propriedade em vez de gravar diretamente em seu ChildMessage
parâmetro. Para obter mais informações, consulte componentes ASP.NET
CoreRazor.

Shared/NestedGrandchild.razor :

razor

<div class="border rounded m-1 p-1">


<h3>Grandchild Component</h3>

<p>Grandchild Message: <b>@GrandchildMessage</b></p>

<p>
<button @onclick="ChangeValue">Change from Grandchild</button>
</p>
</div>

@code {
[Parameter]
public string? GrandchildMessage { get; set; }

[Parameter]
public EventCallback<string> GrandchildMessageChanged { get; set; }

private async Task ChangeValue()


{
await GrandchildMessageChanged.InvokeAsync(
$"Set in Grandchild {DateTime.Now}");
}
}

Para obter uma abordagem alternativa adequada para compartilhar dados na memória
e entre componentes que não são necessariamente aninhados, consulte ASP.NET Core
Blazor gerenciamento de estado.

Recursos adicionais
Detecção de alteração de parâmetro e diretrizes adicionais sobre Razor
renderização de componentes
Blazor ASP.NET Core formulários e componentes de entrada
Associação a botões de opção em um formulário
Opções de associação InputSelect a valores de objeto null C#
Blazor ASP.NET Core manipulação de eventos: EventCallback seção
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Blazor manipulação de eventos ASP.NET
Core
Artigo • 29/11/2022 • 42 minutos para o fim da leitura

Este artigo explica Blazoros recursos de tratamento de eventos, incluindo tipos de


argumento de evento, retornos de chamada de evento e gerenciamento de eventos
padrão do navegador.

Especifique manipuladores de eventos delegados na Razor marcação de componente


com @on{DOM EVENT}="{DELEGATE}"Razor sintaxe:

O {DOM EVENT} espaço reservado é um evento DOM (Document Object Model)


(por exemplo, click ).
O {DELEGATE} espaço reservado é o manipulador de eventos delegado C#.

Para tratamento de eventos:

Há suporte para manipuladores de eventos delegados assíncronos que retornam


um Task .
Os manipuladores de eventos delegados disparam automaticamente uma
renderização de interface do usuário, portanto, não é necessário chamar
StateHasChangedmanualmente .
As exceções são registradas.

O seguinte código:

Chama o UpdateHeading método quando o botão é selecionado na interface do


usuário.
Chama o CheckChanged método quando a caixa de seleção é alterada na interface
do usuário.

Pages/EventHandlerExample1.razor :

razor

@page "/event-handler-example-1"

<h1>@currentHeading</h1>

<p>
<label>
New title
<input @bind="newHeading" />
</label>
<button @onclick="UpdateHeading">
Update heading
</button>
</p>

<p>
<label>
<input type="checkbox" @onchange="CheckChanged" />
@checkedMessage
</label>
</p>

@code {
private string currentHeading = "Initial heading";
private string? newHeading;
private string checkedMessage = "Not changed yet";

private void UpdateHeading()


{
currentHeading = $"{newHeading}!!!";
}

private void CheckChanged()


{
checkedMessage = $"Last changed at {DateTime.Now}";
}
}

No exemplo a seguir, UpdateHeading :

É chamado de forma assíncrona quando o botão é selecionado.


Aguarda dois segundos antes de atualizar o título.

Pages/EventHandlerExample2.razor :

razor

@page "/event-handler-example-2"

<h1>@currentHeading</h1>

<p>
<label>
New title
<input @bind="newHeading" />
</label>
<button @onclick="UpdateHeading">
Update heading
</button>
</p>

@code {
private string currentHeading = "Initial heading";
private string? newHeading;

private async Task UpdateHeading()


{
await Task.Delay(2000);

currentHeading = $"{newHeading}!!!";
}
}

Argumentos de evento

Argumentos de evento internos


Para eventos que dão suporte a um tipo de argumento de evento, especificar um
parâmetro de evento na definição do método de evento só será necessário se o tipo de
evento for usado no método . No exemplo a seguir, MouseEventArgs é usado no
método para definir o texto da ReportPointerLocation mensagem que relata as
coordenadas do mouse quando o usuário seleciona um botão na interface do usuário.

Pages/EventHandlerExample3.razor :

razor

@page "/event-handler-example-3"

@for (var i = 0; i < 4; i++)


{
<p>
<button @onclick="ReportPointerLocation">
Where's my mouse pointer for this button?
</button>
</p>
}

<p>@mousePointerMessage</p>

@code {
private string? mousePointerMessage;

private void ReportPointerLocation(MouseEventArgs e)


{
mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
}
}
EventArgs O suporte é mostrado na tabela a seguir.

Evento Classe Anotações do DOM (Modelo de Objeto do


Documento)

Área de ClipboardEventArgs
Transferência

Arrastar DragEventArgs DataTransfer e DataTransferItem segure os dados do item


arrastado.

Implemente arrastar e soltar em Blazor aplicativos usando


JS a interoperabilidade com a API de Arrastar e Soltar
HTML .

Erro do ErrorEventArgs

Evento EventArgs EventHandlers contém atributos para configurar os


mapeamentos entre nomes de eventos e tipos de
argumento de evento.

Foco FocusEventArgs Não inclui suporte para relatedTarget .

Entrada ChangeEventArgs

Keyboard KeyboardEventArgs

Mouse MouseEventArgs

Ponteiro do PointerEventArgs
mouse

Botão de WheelEventArgs
rolagem do
mouse

Progresso ProgressEventArgs

Touch TouchEventArgs TouchPoint representa um único ponto de contato em um


dispositivo sensível ao toque.

Para saber mais, consulte os recursos a seguir:

EventArgsclasses na origem de referência ASP.NET Core (branch


dotnet/aspnetcoremain)

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

EventHandlers contém atributos para configurar os mapeamentos entre nomes de


eventos e tipos de argumento de evento.

Argumentos de evento personalizados


Blazor dá suporte a argumentos de evento personalizados, que permitem passar dados
arbitrários para manipuladores de eventos do .NET com eventos personalizados.

Configuração geral
Eventos personalizados com argumentos de evento personalizados geralmente são
habilitados com as etapas a seguir.

1. Em JavaScript, defina uma função para criar o objeto de argumento de evento


personalizado do evento de origem:

JavaScript

function eventArgsCreator(event) {
return {
customProperty1: 'any value for property 1',
customProperty2: event.srcElement.value
};
}

2. Registre o evento personalizado com o manipulador anterior em


wwwroot/index.html (Blazor WebAssembly) ou Pages/_Host.cshtml (Blazor Server)

imediatamente após o Blazor <script> :

HTML

<script>
Blazor.registerCustomEventType('customevent', {
createEventArgs: eventArgsCreator
});
</script>

7 Observação
A chamada para registerCustomEventType é executada em um script apenas
uma vez por evento.

3. Defina uma classe para os argumentos de evento:

C#

public class CustomEventArgs : EventArgs


{
public string? CustomProperty1 {get; set;}
public string? CustomProperty2 {get; set;}
}

4. Conecte o evento personalizado com os argumentos de evento adicionando uma


EventHandlerAttribute anotação de atributo para o evento personalizado. A classe
não requer membros. Observe que a classe deve ser chamada EventHandlers para
ser encontrada pelo Razor compilador, mas você deve colocá-la em um
namespace específico ao seu aplicativo:

C#

[EventHandler("oncustomevent", typeof(CustomEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

5. Registre o manipulador de eventos em um ou mais elementos HTML. Acesse os


dados que foram passados do JavaScript no método de manipulador delegado:

razor

<button @oncustomevent="HandleCustomEvent">Handle</button>

@code
{
private void HandleCustomEvent(CustomEventArgs eventArgs)
{
// eventArgs.CustomProperty1
// eventArgs.CustomProperty2
}
}

Se o @oncustomevent atributo não for reconhecido pelo IntelliSense, verifique se o


componente ou o _Imports.razor arquivo contém uma instrução @using para o
namespace que contém a EventHandler classe .
Sempre que o evento personalizado é acionado no DOM, o manipulador de eventos é
chamado com os dados passados do JavaScript.

Se você estiver tentando disparar um evento personalizado, bubbles deverá ser


habilitado definindo seu valor como true . Caso contrário, o evento não alcançará o
Blazor manipulador para processamento no método personalizado
EventHandlerAttribute C#. Para obter mais informações, consulte MDN Web Docs:
propagação de eventos .

Exemplo de evento de colagem de área de transferência


personalizada
O exemplo a seguir recebe um evento de colagem de área de transferência
personalizada que inclui a hora da colagem e o texto colado do usuário.

Declare um nome personalizado ( oncustompaste ) para o evento e uma classe .NET


( CustomPasteEventArgs ) para manter os argumentos de evento para este evento:

CustomEvents.cs :

C#

[EventHandler("oncustompaste", typeof(CustomPasteEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

public class CustomPasteEventArgs : EventArgs


{
public DateTime EventTimestamp { get; set; }
public string? PastedData { get; set; }
}

Adicione código JavaScript para fornecer dados para a EventArgs subclasse.


wwwroot/index.html No arquivo ou Pages/_Host.cshtml , adicione a marca e o conteúdo

a seguir <script> imediatamente após o Blazor script. O exemplo a seguir trata apenas
o texto de colagem, mas você pode usar APIs JavaScript arbitrárias para lidar com
usuários colando outros tipos de dados, como imagens.

wwwroot/index.html (Blazor WebAssembly) ou Pages/_Host.cshtml (Blazor Server)


imediatamente após o Blazor script:

HTML
<script>
Blazor.registerCustomEventType('custompaste', {
browserEventName: 'paste',
createEventArgs: event => {
return {
eventTimestamp: new Date(),
pastedData: event.clipboardData.getData('text')
};
}
});
</script>

O código anterior informa ao navegador que, quando ocorre um evento nativo paste :

Gerar um custompaste evento.


Forneça os dados de argumentos de evento usando a lógica personalizada
declarada:
Para o eventTimestamp , crie uma nova data.
Para o pastedData , obtenha os dados da área de transferência como texto. Para
obter mais informações, consulte MDN Web Docs:
ClipboardEvent.clipboardData .

As convenções de nome do evento diferem entre .NET e JavaScript:

No .NET, os nomes de eventos são prefixados com " on ".


No JavaScript, os nomes de eventos não têm um prefixo.

Em um Razor componente, anexe o manipulador personalizado a um elemento .

Pages/CustomPasteArguments.razor :

razor

@page "/custom-paste-arguments"

<label>
Try pasting into the following text box:
<input @oncustompaste="HandleCustomPaste" />
</label>

<p>
@message
</p>

@code {
private string? message;

private void HandleCustomPaste(CustomPasteEventArgs eventArgs)


{
message = $"At {eventArgs.EventTimestamp.ToShortTimeString()}, " +
$"you pasted: {eventArgs.PastedData}";
}
}

Expressões lambda
Há suporte para expressões Lambda como o manipulador de eventos delegado.

Pages/EventHandlerExample4.razor :

razor

@page "/event-handler-example-4"

<h1>@heading</h1>

<p>
<button @onclick="@(e => heading = "New heading!!!")">
Update heading
</button>
</p>

@code {
private string heading = "Initial heading";
}

Geralmente, é conveniente fechar valores adicionais usando parâmetros de método C#,


como ao iterar em um conjunto de elementos. O exemplo a seguir cria três botões, cada
um dos quais chama UpdateHeading e passa os seguintes dados:

Um argumento de evento (MouseEventArgs) em e .


O número do botão em buttonNumber .

Pages/EventHandlerExample5.razor :

razor

@page "/event-handler-example-5"

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)


{
var buttonNumber = i;

<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}

@code {
private string heading = "Select a button to learn its position";

private void UpdateHeading(MouseEventArgs e, int buttonNumber)


{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}

7 Observação

Não use uma variável de loop diretamente em uma expressão lambda, como i no
exemplo de loop anterior for . Caso contrário, a mesma variável é usada por todas
as expressões lambda, o que resulta no uso do mesmo valor em todas as lambdas.
Sempre capture o valor da variável em uma variável local e use-o. No exemplo
anterior:

A variável i de loop é atribuída a buttonNumber .


buttonNumber é usado na expressão lambda.

A criação de um grande número de delegados de eventos em um loop pode causar um


desempenho de renderização ruim. Para obter mais informações, consulte ASP.NET Core
Blazor práticas recomendadas de desempenho.

EventCallback
Um cenário comum com componentes aninhados executa o método de um
componente pai quando ocorre um evento de componente filho. Um onclick evento
que ocorre no componente filho é um caso de uso comum. Para expor eventos entre
componentes, use um EventCallback. Um componente pai pode atribuir um método de
retorno de chamada a um componente EventCallbackfilho.

Child O componente a seguir demonstra como o manipulador de onclick um botão é

configurado para receber um EventCallback delegado do da ParentComponent amostra. O


EventCallback é digitado com MouseEventArgs , que é apropriado para um onclick
evento de um dispositivo periférico.
Shared/Child.razor :

razor

<p>
<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>
</p>

@code {
[Parameter]
public string? Title { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }

[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

O Parent componente define o filho EventCallback<TValue> ( OnClickCallback ) como


seu ShowMessage método.

Pages/Parent.razor :

razor

@page "/parent"

<h1>Parent-child example</h1>

<Child Title="Panel Title from Parent" OnClickCallback="@ShowMessage">


Content of the child component is supplied by the parent component.
</Child>

<p>@message</p>

@code {
private string? message;

private void ShowMessage(MouseEventArgs e)


{
message = $"Blaze a new trail with Blazor! ({e.ScreenX}:
{e.ScreenY})";
}
}

Quando o botão é selecionado no ChildComponent :


O Parent método do ShowMessage componente é chamado. message é atualizado
e exibido no Parent componente .
Uma chamada para StateHasChanged não é necessária no método do retorno de
chamada ( ShowMessage ). StateHasChanged é chamado automaticamente para
rerender o Parent componente, assim como eventos filho disparam a rerendering
de componente em manipuladores de eventos que são executados dentro do
filho. Para saber mais, consulte Renderização de componentes Razor no ASP.NET
Core.

EventCallback e EventCallback<TValue> permitem delegados assíncronos. EventCallback


é fracamente tipado e permite passar qualquer argumento de tipo em
InvokeAsync(Object) . EventCallback<TValue> é fortemente tipado e requer a passagem

de um T argumento no InvokeAsync(T) que é atribuível a TValue .

razor

<ChildComponent
OnClickCallback="@(async () => { await Task.Yield(); messageText =
"Blaze It!"; })" />

Invoque um EventCallback ou EventCallback<TValue> com InvokeAsync e aguarde o


Task:

C#

await OnClickCallback.InvokeAsync(arg);

Use EventCallback e EventCallback<TValue> para tratamento de eventos e parâmetros


de componente de associação.

Prefira o fortemente tipado EventCallback<TValue> em vez EventCallbackde .


EventCallback<TValue> fornece comentários de erro aprimorados para os usuários do
componente. Semelhante a outros manipuladores de eventos de interface do usuário,
especificar o parâmetro de evento é opcional. Use EventCallback quando não houver
nenhum valor passado para o retorno de chamada.

Impedir ações padrão


Use o @on{DOM EVENT}:preventDefault atributo de diretiva para impedir a ação padrão
de um evento, em que o {DOM EVENT} espaço reservado é um evento DOM (Modelo de
Objeto de Documento).
Quando uma chave é selecionada em um dispositivo de entrada e o foco do elemento
está em uma caixa de texto, um navegador normalmente exibe o caractere da chave na
caixa de texto. No exemplo a seguir, o comportamento padrão é impedido
especificando o @onkeydown:preventDefault atributo de diretiva . Quando o foco está no
<input> elemento , o contador incrementa com a sequência de teclas Shift + + . O +

caractere não é atribuído ao <input> valor do elemento. Para obter mais informações
sobre keydown , consulte MDN Web Docs: Document: keydown event .

Pages/EventHandlerExample6.razor :

razor

@page "/event-handler-example-6"

<p>
<input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault
/>
</p>

@code {
private int count = 0;

private void KeyHandler(KeyboardEventArgs e)


{
if (e.Key == "+")
{
count++;
}
}
}

Especificar o @on{DOM EVENT}:preventDefault atributo sem um valor é equivalente a


@on{DOM EVENT}:preventDefault="true" .

Uma expressão também é um valor permitido do atributo. No exemplo a seguir,


shouldPreventDefault é um bool campo definido como true ou false :

razor

<input @onkeydown:preventDefault="shouldPreventDefault" />

...

@code {
private bool shouldPreventDefault = true;
}
Interromper propagação de eventos
Use o @on{DOM EVENT}:stopPropagation atributo de diretiva para interromper a
propagação de eventos dentro do Blazor escopo. {DOM EVENT} é um espaço reservado
para um evento DOM (Modelo de Objeto de Documento).

O stopPropagation efeito do atributo de diretiva é limitado ao Blazor escopo e não se


estende ao DOM HTML. Os eventos devem se propagar para a raiz HTML DOM antes
Blazor que possam agir sobre eles. Para um mecanismo para impedir a propagação de
eventos HTML DOM, considere a seguinte abordagem:

Obtenha o caminho do evento chamando Event.composedPath() .


Filtrar eventos com base nos destinos de eventos compostos (EventTarget) .

No exemplo a seguir, marcar a caixa de seleção impede que eventos de clique do


segundo filho <div> se propaguem para o pai <div> . Como eventos de clique
propagados normalmente disparam o OnSelectParentDiv método , selecionar o
segundo filho <div> resulta na mensagem pai <div> que aparece, a menos que a caixa
de seleção esteja selecionada.

Pages/EventHandlerExample7.razor :

razor

@page "/event-handler-example-7"

<label>
<input @bind="stopPropagation" type="checkbox" />
Stop Propagation
</label>

<div class="m-1 p-1 border border-primary" @onclick="OnSelectParentDiv">


<h3>Parent div</h3>

<div class="m-1 p-1 border" @onclick="OnSelectChildDiv">


Child div that doesn't stop propagation when selected.
</div>

<div class="m-1 p-1 border" @onclick="OnSelectChildDiv"


@onclick:stopPropagation="stopPropagation">
Child div that stops propagation when selected.
</div>
</div>

<p>
@message
</p>
@code {
private bool stopPropagation = false;
private string? message;

private void OnSelectParentDiv() =>


message = $"The parent div was selected. {DateTime.Now}";

private void OnSelectChildDiv() =>


message = $"A child div was selected. {DateTime.Now}";
}

Concentrar um elemento
Chame FocusAsync em uma referência de elemento para concentrar um elemento no
código. No exemplo a seguir, selecione o botão para concentrar o <input> elemento.

Pages/EventHandlerExample8.razor :

razor

@page "/event-handler-example-8"

<p>
<input @ref="exampleInput" />
</p>

<button @onclick="ChangeFocus">
Focus the Input Element
</button>

@code {
private ElementReference exampleInput;

private async Task ChangeFocus()


{
await exampleInput.FocusAsync();
}
}
Razor ASP.NET Core ciclo de vida do
componente
Artigo • 06/01/2023 • 95 minutos para o fim da leitura

Este artigo explica o ciclo de vida do componente ASP.NET Core Razor e como usar
eventos de ciclo de vida.

O Razor componente processa Razor eventos de ciclo de vida do componente em um


conjunto de métodos de ciclo de vida síncronos e assíncronos. Os métodos de ciclo de
vida podem ser substituídos para executar operações adicionais em componentes
durante a inicialização e a renderização do componente.

Eventos de ciclo de vida


Os diagramas simplificados a seguir ilustram Razor o processamento de eventos do
ciclo de vida do componente. Os métodos C# associados aos eventos de ciclo de vida
são definidos com exemplos nas seções a seguir deste artigo.

Eventos do ciclo de vida do componente:

1. Se o componente estiver sendo renderizado pela primeira vez em uma solicitação:

Crie a instância do componente.


Executar injeção de propriedade. Execute SetParametersAsync.
Chame OnInitialized{Async}. Se um incompleto Task for retornado, o Task será
aguardado e, em seguida, o componente será gerado novamente.

2. Chame OnParametersSet{Async}. Se um incompleto Task for retornado, o Task será


aguardado e, em seguida, o componente será gerado novamente.
3. Renderize para todo o trabalho síncrono e conclua Tasks.

7 Observação

Ações assíncronas executadas em eventos de ciclo de vida podem não ter sido
concluídas antes de um componente ser renderizado. Para obter mais informações,
consulte a seção Manipular ações assíncronas incompletas na renderização mais
adiante neste artigo.

Um componente pai é renderizado antes de seus componentes filhos porque a


renderização é o que determina quais filhos estão presentes. Se a inicialização do
componente pai síncrono for usada, a inicialização pai será concluída primeiro. Se a
inicialização de componente pai assíncrono for usada, a ordem de conclusão da
inicialização do componente pai e filho não poderá ser determinada porque depende
do código de inicialização em execução.

Processamento de eventos do DOM (Document Object Model):

1. O manipulador de eventos é executado.


2. Se um incompleto Task for retornado, o Task será aguardado e, em seguida, o
componente será gerado novamente.
3. Renderize para todo o trabalho síncrono e conclua Tasks.
O Render ciclo de vida:

1. Evite mais operações de renderização no componente:

Após a primeira renderização.


Quando ShouldRender é false .

2. Crie a diferença (diferença) da árvore de renderização e renderize o componente.


3. Aguarde o DOM para atualizar.
4. Chame OnAfterRender{Async}.
Chamadas do desenvolvedor para StateHasChanged resultar em uma renderização. Para
saber mais, consulte Renderização de componentes Razor no ASP.NET Core.

Este artigo simplifica alguns aspectos do processamento de eventos do ciclo de vida do


componente para esclarecer a lógica de estrutura complexa. Talvez seja necessário
acessar a fonte de referência para integrar o ComponentBase processamento de
eventos personalizados ao Blazorprocessamento de eventos do ciclo de vida. Os
comentários de código na fonte de referência incluem comentários adicionais sobre o
processamento de eventos do ciclo de vida que não aparecem neste artigo ou na
documentação da API. Observe que Blazoro processamento de eventos do ciclo de vida
foi alterado ao longo do tempo e está sujeito a alterações sem aviso prévio a cada
versão.

7 Observação
Os links de documentação para a fonte de referência do .NET geralmente carregam
o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Quando os parâmetros são definidos


( SetParametersAsync )
SetParametersAsync define parâmetros fornecidos pelo pai do componente na árvore
de renderização ou nos parâmetros de rota.

O parâmetro do ParameterView método contém o conjunto de valores de parâmetro de


componente para o componente sempre que SetParametersAsync é chamado. Ao
substituir o método , o SetParametersAsync código do desenvolvedor pode interagir
diretamente com ParameterViewos parâmetros de .

A implementação padrão de SetParametersAsync define o valor de cada propriedade


com o [Parameter] atributo ou [CascadingParameter] que tem um valor correspondente
no ParameterView. Os parâmetros que não têm um valor correspondente em
ParameterView são deixados inalterados.

Se base.SetParametersAsync não for invocado, o código do desenvolvedor poderá


interpretar os valores dos parâmetros de entrada de qualquer maneira necessária. Por
exemplo, não há nenhum requisito para atribuir os parâmetros de entrada às
propriedades da classe .

Se os manipuladores de eventos forem fornecidos no código do desenvolvedor,


remova-os do descarte. Para obter mais informações, consulte a seção Descarte de
componentes com IDisposableIAsyncDisposable .

No exemplo a seguir, ParameterView.TryGetValue atribui o Param valor do parâmetro a


value se a análise de um parâmetro de rota para Param for bem-sucedida. Quando
value não null é , o valor é exibido pelo componente .

Embora a correspondência de parâmetros de rota não diferencia maiúsculas de


minúsculas, TryGetValue corresponde apenas aos nomes de parâmetros que diferenciam
maiúsculas de minúsculas no modelo de rota. O exemplo a seguir requer o uso de no
modelo de /{Param?} rota para obter o valor com TryGetValue, não /{param?} . Se
/{param?} for usado nesse cenário, TryGetValue retornará false e message não será

definido como nenhuma das cadeias de caracteres message .

Pages/SetParamsAsync.razor :

razor

@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
private string message = "Not set";

[Parameter]
public string? Param { get; set; }

public override async Task SetParametersAsync(ParameterView parameters)


{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}

await base.SetParametersAsync(parameters);
}
}

Inicialização de componente
( OnInitialized{Async} )
OnInitialized e OnInitializedAsync são invocados quando o componente é inicializado
depois de ter recebido seus parâmetros iniciais em SetParametersAsync.

Se a inicialização do componente pai síncrono for usada, a inicialização pai será


garantida para ser concluída antes da inicialização do componente filho. Se a
inicialização de componente pai assíncrono for usada, a ordem de conclusão da
inicialização do componente pai e filho não poderá ser determinada porque depende
do código de inicialização em execução.
Para uma operação síncrona, substitua OnInitialized:

Pages/OnInit.razor :

razor

@page "/on-init"

<p>@message</p>

@code {
private string? message;

protected override void OnInitialized()


{
message = $"Initialized at {DateTime.Now}";
}
}

Para executar uma operação assíncrona, substitua OnInitializedAsync e use o await


operador :

C#

protected override async Task OnInitializedAsync()


{
await ...
}

Blazoraplicativos que geram previamente seu conteúdo na chamada OnInitializedAsync


do servidor duas vezes:

Uma vez quando o componente é renderizado estaticamente inicialmente como


parte da página.
Uma segunda vez em que o navegador renderiza o componente.

Para impedir que o código do desenvolvedor no OnInitializedAsync seja executado duas


vezes durante a pré-geração, consulte a seção Reconexão com estado após a pré-
geração . Embora o conteúdo na seção se concentre na Blazor Serverreconexão com
SignalR estado, o cenário de pré-geração em aplicativos hospedados Blazor
WebAssembly (WebAssemblyPrerendered) envolve condições e abordagens
semelhantes para impedir a execução do código do desenvolvedor duas vezes. Para
preservar o estado durante a execução do código de inicialização durante a pré-
geração, consulte Prerender e integre ASP.NET Core Razor componentes.
Embora um Blazor aplicativo esteja pré-gerando, determinadas ações, como chamar em
JavaScript (JS interoperabilidade), não são possíveis. Os componentes podem precisar
ser renderizados de forma diferente quando pré-gerados. Para obter mais informações,
consulte a seção Pré-geração com interoperabilidade do JavaScript .

Se os manipuladores de eventos forem fornecidos no código do desenvolvedor,


remova-os do descarte. Para obter mais informações, consulte a seção Descarte de
componentes com IDisposableIAsyncDisposable .

Depois que os parâmetros forem definidos


( OnParametersSet{Async} )
OnParametersSet ou OnParametersSetAsync são chamados:

Depois que o componente é inicializado em OnInitialized ou OnInitializedAsync.

Quando o componente pai remete e fornece:


Tipos imutáveis conhecidos ou primitivos quando pelo menos um parâmetro foi
alterado.
Parâmetros de tipo complexo. A estrutura não pode saber se os valores de um
parâmetro de tipo complexo sofreram mutação interna, portanto, a estrutura
sempre trata o conjunto de parâmetros como alterado quando um ou mais
parâmetros de tipo complexo estão presentes.

Para obter mais informações sobre convenções de renderização, consulte ASP.NET


Core Razor renderização de componentes.

Para o seguinte componente de exemplo, navegue até a página do componente em


uma URL:

Com uma data de início recebida por StartDate : /on-parameters-set/2021-03-19


Sem uma data de início, em que StartDate é atribuído um valor da hora local
atual: /on-parameters-set

Pages/OnParamsSet.razor :

7 Observação

Em uma rota de componente, não é possível restringir um DateTime parâmetro


com a restrição datetimede rota e tornar o parâmetro opcional. Portanto, o
componente a seguir OnParamsSet usa duas @page diretivas para lidar com o
roteamento com e sem um segmento de data fornecido na URL.
razor

@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
private string? message;

[Parameter]
public DateTime StartDate { get; set; }

protected override void OnParametersSet()


{
if (StartDate == default)
{
StartDate = DateTime.Now;

message = $"No start date in URL. Default value applied


(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate:
{StartDate}).";
}
}
}

O trabalho assíncrono ao aplicar parâmetros e valores de propriedade deve ocorrer


durante o evento de OnParametersSetAsync ciclo de vida:

C#

protected override async Task OnParametersSetAsync()


{
await ...
}

Se os manipuladores de eventos forem fornecidos no código do desenvolvedor,


remova-os do descarte. Para obter mais informações, consulte a seção Descarte de
componentes com IDisposableIAsyncDisposable .

Para obter mais informações sobre parâmetros e restrições de rota, consulte ASP.NET
Core Blazor roteamento e navegação.
Após a renderização do componente
( OnAfterRender{Async} )
OnAfterRender e OnAfterRenderAsync são chamados depois que um componente
termina a renderização. As referências de elemento e componente são preenchidas
neste ponto. Use esse estágio para executar etapas de inicialização adicionais com o
conteúdo renderizado, como JS chamadas de interoperabilidade que interagem com os
elementos DOM renderizados.

O firstRender parâmetro para OnAfterRender e OnAfterRenderAsync:

É definido como true a primeira vez que a instância do componente é


renderizada.
Pode ser usado para garantir que o trabalho de inicialização seja executado apenas
uma vez.

Pages/AfterRender.razor :

razor

@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger

<button @onclick="LogInformation">Log information (and trigger a render)


</button>

@code {
private string message = "Initial assigned message.";

protected override void OnAfterRender(bool firstRender)


{
Logger.LogInformation("OnAfterRender(1): firstRender: " +
"{FirstRender}, message: {Message}", firstRender, message);

if (firstRender)
{
message = "Executed for the first render.";
}
else
{
message = "Executed after the first render.";
}

Logger.LogInformation("OnAfterRender(2): firstRender: " +


"{FirstRender}, message: {Message}", firstRender, message);
}

private void LogInformation()


{
Logger.LogInformation("LogInformation called");
}
}

O trabalho assíncrono imediatamente após a renderização deve ocorrer durante o


evento do OnAfterRenderAsync ciclo de vida:

C#

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
await ...
}
}

Mesmo que você retorne um Task de OnAfterRenderAsync, a estrutura não agenda um


ciclo de renderização adicional para o componente depois que essa tarefa for concluída.
Isso é para evitar um loop de renderização infinito. Isso é diferente dos outros métodos
de ciclo de vida, que agendam um ciclo de renderização adicional depois que um
retornado Task é concluído.

OnAfterRender e OnAfterRenderAsyncnão são chamados durante o processo de pré-


geração no servidor. Os métodos são chamados quando o componente é renderizado
interativamente após a pré-geração. Quando os pré-remetentes do aplicativo:

1. O componente é executado no servidor para produzir alguma marcação HTML


estática na resposta HTTP. Durante essa fase, OnAfterRender e
OnAfterRenderAsync não são chamados.
2. Quando o Blazor script ( blazor.webassembly.js ou blazor.server.js ) é iniciado no
navegador, o componente é reiniciado em um modo de renderização interativo.
Depois que um componente é reiniciado, OnAfterRender e
OnAfterRenderAsyncsão chamados porque o aplicativo não está mais na fase de
pré-geração.

Se os manipuladores de eventos forem fornecidos no código do desenvolvedor,


remova-os no descarte. Para obter mais informações, consulte a seção Descarte de
componentes com IDisposableIAsyncDisposable .

Alterações de estado ( StateHasChanged )


StateHasChanged notifica o componente de que seu estado foi alterado. Quando
aplicável, chamar StateHasChanged faz com que o componente seja rerender.

StateHasChanged é chamado automaticamente para EventCallback métodos. Para obter


mais informações sobre retornos de chamada de evento, consulte ASP.NET Core Blazor
tratamento de eventos.

Para obter mais informações sobre renderização de componentes e quando chamar


StateHasChanged, incluindo quando invocá-lo com ComponentBase.InvokeAsync,
consulte ASP.NET Core Razor renderização de componentes.

Manipular ações assíncronas incompletas na


renderização
Ações assíncronas executadas em eventos de ciclo de vida podem não ter sido
concluídas antes de o componente ser renderizado. Objetos podem ser null ou
preenchidos incompletamente com dados enquanto o método de ciclo de vida está em
execução. Forneça lógica de renderização para confirmar se os objetos são inicializados.
Renderize elementos de interface do usuário do espaço reservado (por exemplo, uma
mensagem de carregamento) enquanto os objetos são null .

FetchData No componente dos Blazor modelos, OnInitializedAsync é substituído para

receber dados de previsão de forma assíncrona ( forecasts ). Quando forecasts é null ,


uma mensagem de carregamento é exibida para o usuário. Depois que o Task
retornado por OnInitializedAsync for concluído, o componente será rerender com o
estado atualizado.

Pages/FetchData.razor no Blazor Server modelo:

razor

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)


{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<!-- forecast data in table element content -->
</table>
}

@code {
private WeatherForecast[]? forecasts;

protected override async Task OnInitializedAsync()


{
forecasts = await
ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
}
}

Tratar erros
Para obter informações sobre como lidar com erros durante a execução do método de
ciclo de vida, consulte Manipular erros em aplicativos ASP.NET CoreBlazor.

Reconexão com estado após a pré-geração


Em um Blazor Server aplicativo quando RenderMode é ServerPrerendered, o
componente é inicialmente renderizado estaticamente como parte da página. Depois
que o navegador estabelece uma SignalR conexão de volta com o servidor, o
componente é renderizado novamente e interativo. Se o método de OnInitialized{Async}
ciclo de vida para inicializar o componente estiver presente, o método será executado
duas vezes:

Quando o componente é pré-gerado estaticamente.


Depois que a conexão do servidor for estabelecida.

Isso pode resultar em uma alteração perceptível nos dados exibidos na interface do
usuário quando o componente é finalmente renderizado. Para evitar esse
comportamento de renderização dupla em um Blazor Server aplicativo, passe um
identificador para armazenar o estado em cache durante a pré-geração e recuperar o
estado após a pré-geração.

O código a seguir demonstra uma atualização WeatherForecastService em um aplicativo


baseado em Blazor Server modelo que evita a renderização dupla. No exemplo a seguir,
o aguardado Delay ( await Task.Delay(...) ) simula um pequeno atraso antes de
retornar dados do GetForecastAsync método .

WeatherForecastService.cs :
C#

using Microsoft.Extensions.Caching.Memory;

public class WeatherForecastService


{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

public WeatherForecastService(IMemoryCache memoryCache)


{
MemoryCache = memoryCache;
}

public IMemoryCache MemoryCache { get; }

public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)


{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});

var rng = new Random();

await Task.Delay(TimeSpan.FromSeconds(10));

return Enumerable.Range(1, 5).Select(index => new


WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}

Para obter mais informações sobre o RenderMode, consulte ASP.NET Core BlazorSignalR
diretrizes.

Embora o conteúdo nesta seção se concentre na Blazor Serverreconexão com SignalR


estado, o cenário de pré-geração em aplicativos hospedados Blazor WebAssembly
(WebAssemblyPrerendered) envolve condições e abordagens semelhantes para impedir
a execução do código do desenvolvedor duas vezes. Para preservar o estado durante a
execução do código de inicialização durante a pré-geração, consulte Prerender e integre
ASP.NET Core Razor componentes.

Pré-geração com interoperabilidade do


JavaScript
Esta seção se aplica a Blazor Server aplicativos hospedados Blazor WebAssembly e que
pré-geram Razor componentes. A pré-geração é abordada em Prerender e integra
componentes ASP.NET CoreRazor.

Embora um aplicativo esteja pré-gerando, determinadas ações, como chamar o


JavaScript (JS), não são possíveis.

Para o exemplo a seguir, a setElementText1 função é colocada dentro do <head>


elemento . A função é chamada com JSRuntimeExtensions.InvokeVoidAsync e não
retorna um valor.

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).

HTML

<script>
window.setElementText1 = (element, text) => element.innerText = text;
</script>

2 Aviso

O exemplo anterior modifica o DOM (Modelo de Objeto de Documento)


diretamente para fins de demonstração. A modificação direta do DOM com JS não
é recomendada na maioria dos cenários porque JS pode interferir no
Blazorcontrole de alterações. Para obter mais informações, consulte ASP.NET Core
Blazor interoperabilidade do JavaScript (JSinteroperabilidade).

O OnAfterRender{Async} evento de ciclo de vida não é chamado durante o processo de


pré-geração no servidor. Substitua o OnAfterRender{Async} método para atrasar JS
chamadas de interoperabilidade até que o componente seja renderizado e interativo no
cliente após a pré-geração.

Pages/PrerenderedInterop1.razor :

razor

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<div @ref="divElement">Text during render</div>

@code {
private ElementReference divElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
await JS.InvokeVoidAsync(
"setElementText1", divElement, "Text after render");
}
}
}

7 Observação

O exemplo anterior polui o cliente com métodos globais. Para obter uma
abordagem melhor em aplicativos de produção, consulte Isolamento de JavaScript
em módulos JavaScript.

Exemplo:

JavaScript

export setElementText1 = (element, text) => element.innerText = text;

O componente a seguir demonstra como usar JS a interoperabilidade como parte da


lógica de inicialização de um componente de uma maneira compatível com a pré-
geração. O componente mostra que é possível disparar uma atualização de
renderização de dentro OnAfterRenderAsyncde . O desenvolvedor deve ter cuidado para
evitar a criação de um loop infinito nesse cenário.

Para o exemplo a seguir, a setElementText2 função é colocada dentro do <head>


elemento . A função é chamada com IJSRuntime.InvokeAsync e retorna um valor.
7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).

HTML

<script>
window.setElementText2 = (element, text) => {
element.innerText = text;
return text;
};
</script>

2 Aviso

O exemplo anterior modifica o DOM (Modelo de Objeto de Documento)


diretamente para fins de demonstração. A modificação direta do DOM com JS não
é recomendada na maioria dos cenários porque JS pode interferir no
Blazorcontrole de alterações. Para obter mais informações, consulte ASP.NET Core
Blazor interoperabilidade do JavaScript (JSinteroperabilidade).

Onde JSRuntime.InvokeAsync é chamado, o ElementReference é usado apenas em


OnAfterRenderAsync e não em nenhum método de ciclo de vida anterior porque não há
nenhum JS elemento até que o componente seja renderizado.

StateHasChangedé chamado para gerar novamente o componente com o novo estado


obtido da JS chamada de interoperabilidade (para obter mais informações, consulte
ASP.NET Core Razor renderização de componentes). O código não cria um loop infinito
porque StateHasChanged é chamado apenas quando data é null .

Pages/PrerenderedInterop2.razor :

razor

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<p>
Get value via JS interop call:
<strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
private string? data;
private ElementReference divElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender && data == null)
{
data = await JS.InvokeAsync<string>(
"setElementText2", divElement, "Hello from interop call!");

StateHasChanged();
}
}
}

7 Observação

O exemplo anterior polui o cliente com métodos globais. Para obter uma
abordagem melhor em aplicativos de produção, consulte Isolamento de JavaScript
em módulos JavaScript.

Exemplo:

JavaScript

export setElementText2 = (element, text) => {


element.innerText = text;
return text;
};

Descarte de componentes com IDisposable e


IAsyncDisposable
Se um componente implementar IDisposable, IAsyncDisposableou ambos, a estrutura
solicitará o descarte de recursos não gerenciados quando o componente for removido
da interface do usuário. O descarte pode ocorrer a qualquer momento, inclusive durante
a inicialização do componente.
Os componentes não devem precisar implementar IDisposable e IAsyncDisposable
simultaneamente. Se ambos forem implementados, a estrutura executará apenas a
sobrecarga assíncrona.

O código do desenvolvedor deve garantir que IAsyncDisposable as implementações não


deem muito tempo para serem concluídas.

Tarefas de limpeza do DOM (Modelo de Objeto de


Documento) durante o descarte de componentes
Não execute JS o código de interoperabilidade para tarefas de limpeza do DOM durante
o descarte de componentes. Em vez disso, use o MutationObserver padrão em
JavaScript no cliente pelos seguintes motivos:

O componente pode ter sido removido do DOM no momento em que o código de


limpeza for executado em Dispose{Async} .
Em um Blazor Server aplicativo, o Blazor renderizador pode ter sido descartado
pela estrutura no momento em que o código de limpeza é executado em
Dispose{Async} .

O MutationObserver padrão permite executar uma função quando um elemento é


removido do DOM.

Para obter diretrizes sobre JSDisconnectedException em aplicativos quando um circuito


é desconectado, consulte Chamar funções JavaScript de métodos .NET em ASP.NET
Core Blazor ou Chamar métodos .NET de funções JavaScript em ASP.NET Core
BlazorBlazor Server . Para obter diretrizes gerais de tratamento de erros de
interoperabilidade do JavaScript, consulte a seção de interoperabilidade JavaScript em
Manipular erros em aplicativos ASP.NET CoreBlazor.

Síncrono IDisposable
Para tarefas de descarte síncronas, use IDisposable.Dispose.

O seguinte componente:

IDisposable Implementa com a @implementsRazor diretiva .


Descarta , obj que é um tipo não gerenciado que implementa IDisposable.
Uma verificação nula é executada porque obj é criada em um método de ciclo de
vida (não mostrado).

razor
@implements IDisposable

...

@code {
...

public void Dispose()


{
obj?.Dispose();
}
}

Se um único objeto exigir descarte, um lambda poderá ser usado para descartar o
objeto quando Dispose for chamado. O exemplo a seguir aparece no artigo
renderização do componente ASP.NET Core Razor e demonstra o uso de uma expressão
lambda para o descarte de um Timer.

Pages/CounterWithTimerDisposal1.razor :

razor

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
private int currentCount = 0;
private Timer timer = new(1000);

protected override void OnInitialized()


{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}

private void OnTimerCallback()


{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}

public void Dispose() => timer.Dispose();


}
7 Observação

No exemplo anterior, a chamada para StateHasChanged é encapsulada por uma


chamada para ComponentBase.InvokeAsync porque o retorno de chamada é
invocado fora do contexto de Blazorsincronização de . Para saber mais, consulte
Renderização de componentes Razor no ASP.NET Core.

Se o objeto for criado em um método de ciclo de vida, como


OnInitialized/OnInitializedAsync, verifique null antes de chamar Dispose .

Pages/CounterWithTimerDisposal2.razor :

razor

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
private int currentCount = 0;
private Timer? timer;

protected override void OnInitialized()


{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}

private void OnTimerCallback()


{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}

public void Dispose() => timer?.Dispose();


}

Para obter mais informações, consulte:

Limpeza de recursos não gerenciados (documentação do .NET)


Operadores condicionais nulos ?. e ?[]

Assíncrono IAsyncDisposable
Para tarefas de descarte assíncronas, use IAsyncDisposable.DisposeAsync.

O seguinte componente:

IAsyncDisposable Implementa com a @implementsRazor diretiva .


Descarta , obj que é um tipo não gerenciado que implementa IAsyncDisposable.
Uma verificação nula é executada porque obj é criada em um método de ciclo de
vida (não mostrado).

razor

@implements IAsyncDisposable

...

@code {
...

public async ValueTask DisposeAsync()


{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}

Para obter mais informações, consulte:

Limpeza de recursos não gerenciados (documentação do .NET)


Operadores condicionais nulos ?. e ?[]

Atribuição de null a objetos descartados


Normalmente, não é necessário atribuir null a objetos descartados depois de
chamar/DisposeDisposeAsync . Casos raros para atribuição null incluem o seguinte:

Se o tipo do objeto for mal implementado e não tolerar chamadas repetidas para
Dispose/DisposeAsync, atribua null após o descarte para ignorar normalmente
outras chamadas para Dispose/DisposeAsync.
Se um processo de longa duração continuar mantendo uma referência a um
objeto descartado, atribuir null permitirá que o coletor de lixo libere o objeto,
apesar do processo de longa duração que contém uma referência a ele.

Esses são cenários incomuns. Para objetos que são implementados corretamente e se
comportam normalmente, não há nenhum ponto em atribuir null a objetos
descartados. Nos casos raros em que um objeto deve ser atribuído null ,
recomendamos documentar o motivo e buscar uma solução que impeça a necessidade
de atribuir null .

StateHasChanged

7 Observação

Não há suporte para chamadasStateHasChanged. Dispose StateHasChanged pode


ser invocado como parte da desativação do renderizador, portanto, não há suporte
para a solicitação de atualizações da interface do usuário nesse ponto.

Manipuladores de eventos
Sempre cancele a assinatura de manipuladores de eventos do .NET. Os seguintes Blazor
exemplos de formulário mostram como cancelar a assinatura de um manipulador de
eventos no Dispose método :

Campo privado e abordagem lambda

razor

@implements IDisposable

<EditForm EditContext="@editContext">
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
...

private EventHandler<FieldChangedEventArgs>? fieldChanged;

protected override void OnInitialized()


{
editContext = new(model);

fieldChanged = (_, __) =>


{
...
};

editContext.OnFieldChanged += fieldChanged;
}

public void Dispose()


{
editContext.OnFieldChanged -= fieldChanged;
}
}

Abordagem de método privado

razor

@implements IDisposable

<EditForm EditContext="@editContext">
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
...

protected override void OnInitialized()


{
editContext = new(model);
editContext.OnFieldChanged += HandleFieldChanged;
}

private void HandleFieldChanged(object sender,


FieldChangedEventArgs e)
{
...
}

public void Dispose()


{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}

Para obter mais informações, consulte a seção Descarte de componentes com


IDisposable e IAsyncDisposable .

Funções, métodos e expressões anônimas


Quando funções anônimas, métodos ou expressões são usados, não é necessário
implementar IDisposable e cancelar a assinatura de delegados. No entanto, não assinar
um delegado é um problema quando o objeto que expõe o evento sobrevive ao
tempo de vida do componente que registra o delegado. Quando isso ocorre, uma
perda de memória resulta porque o delegado registrado mantém o objeto original
ativo. Portanto, use apenas as abordagens a seguir quando souber que o delegado do
evento descarta rapidamente. Quando estiver em dúvida sobre o tempo de vida dos
objetos que exigem descarte, assine um método delegado e descarte corretamente o
delegado como mostram os exemplos anteriores.

Abordagem anônima do método lambda (descarte explícito não necessário):

C#

private void HandleFieldChanged(object sender, FieldChangedEventArgs e)


{
formInvalid = !editContext.Validate();
StateHasChanged();
}

protected override void OnInitialized()


{
editContext = new(starship);
editContext.OnFieldChanged += (s, e) =>
HandleFieldChanged((editContext)s, e);
}

Abordagem de expressão lambda anônima (descarte explícito não necessário):

C#

private ValidationMessageStore? messageStore;

[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }

protected override void OnInitialized()


{
...

messageStore = new(CurrentEditContext);

CurrentEditContext.OnValidationRequested += (s, e) =>


messageStore.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore.Clear(e.FieldIdentifier);
}
O exemplo completo do código anterior com expressões lambda anônimas
aparece no artigo formulários ASP.NET Core Blazor e componentes de entrada.

Para obter mais informações, consulte Limpeza de recursos não gerenciados e os


tópicos que o seguem sobre a implementação dos Dispose métodos e DisposeAsync .

Trabalho em segundo plano cancelável


Os componentes geralmente executam trabalhos em segundo plano de execução longa,
como fazer chamadas de rede (HttpClient) e interagir com bancos de dados. É desejável
interromper o trabalho em segundo plano para conservar recursos do sistema em várias
situações. Por exemplo, as operações assíncronas em segundo plano não param
automaticamente quando um usuário navega para longe de um componente.

Outros motivos pelos quais os itens de trabalho em segundo plano podem exigir
cancelamento incluem:

Uma tarefa em segundo plano em execução foi iniciada com dados de entrada ou
parâmetros de processamento com falha.
O conjunto atual de itens de trabalho em segundo plano deve ser substituído por
um novo conjunto de itens de trabalho.
A prioridade das tarefas em execução no momento deve ser alterada.
O aplicativo deve ser desligado para reimplantação do servidor.
Os recursos do servidor tornam-se limitados, exigindo o reagendamento de itens
de trabalho em segundo plano.

Para implementar um padrão de trabalho em segundo plano cancelável em um


componente:

Use um CancellationTokenSource e CancellationToken.


Ao eliminar o componente e a qualquer momento o cancelamento é desejado
cancelando manualmente o token, chame CancellationTokenSource.Cancel para
sinalizar que o trabalho em segundo plano deve ser cancelado.
Depois que a chamada assíncrona retornar, chame ThrowIfCancellationRequested
no token.

No exemplo a seguir:

await Task.Delay(5000, cts.Token); representa o trabalho em segundo plano

assíncrono de execução longa.


BackgroundResourceMethod representa um método em segundo plano de execução

longa que não deve ser iniciado se o Resource for descartado antes que o método
seja chamado.

Pages/BackgroundWork.razor :

razor

@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>


<button @onclick="Dispose">Trigger Disposal</button>

@code {
private Resource resource = new();
private CancellationTokenSource cts = new();

protected async Task LongRunningWork()


{
Logger.LogInformation("Long running work started");

await Task.Delay(5000, cts.Token);

cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}

public void Dispose()


{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}

private class Resource : IDisposable


{
private bool disposed;

public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)


{
logger.LogInformation("BackgroundResourceMethod: Start method");

if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}

// Take action on the Resource

logger.LogInformation("BackgroundResourceMethod: Action on
Resource");
}

public void Dispose()


{
disposed = true;
}
}
}

Blazor Server eventos de reconexão


Os eventos de ciclo de vida do componente abordados neste artigo operam
separadamente dos manipuladores de eventos deBlazor Server reconexão do . Quando
um Blazor Server aplicativo perde sua SignalR conexão com o cliente, somente as
atualizações da interface do usuário são interrompidas. As atualizações da interface do
usuário são retomadas quando a conexão é restabelecida. Para obter mais informações
sobre eventos e configuração do manipulador de circuitos, consulte ASP.NET Core
BlazorSignalR diretrizes.
Razor virtualização do componente
ASP.NET Core
Artigo • 28/11/2022 • 27 minutos para o fim da leitura

Este artigo explica como usar a virtualização de componentes em aplicativos ASP.NET


CoreBlazor.

Melhore o desempenho percebido da renderização de componentes usando o Blazor


suporte interno de virtualização da estrutura com o Virtualize componente . A
virtualização é uma técnica para limitar a renderização da interface do usuário apenas às
partes que estão visíveis no momento. Por exemplo, a virtualização é útil quando o
aplicativo deve renderizar uma longa lista de itens e apenas um subconjunto de itens é
necessário para ficar visível a qualquer momento.

Use o Virtualize componente (origem de referência) quando:

Renderizando um conjunto de itens de dados em um loop.


A maioria dos itens não está visível devido à rolagem.
Os itens renderizados têm o mesmo tamanho.

Quando o usuário rola para um ponto arbitrário na Virtualize lista de itens do


componente, o componente calcula os itens visíveis a serem mostrados. Itens não vistos
não são renderizados.

Sem virtualização, uma lista típica pode usar um loop C# foreach para renderizar cada
item em uma lista. No exemplo a seguir:

allFlights é uma coleção de voos de avião.

O FlightSummary componente exibe detalhes sobre cada versão de pré-


lançamento.
O @key atributo de diretiva preserva a relação de cada FlightSummary
componente com sua versão de pré-lançamento renderizada pelo do
FlightId voo.

razor

<div style="height:500px;overflow-y:scroll">
@foreach (var flight in allFlights)
{
<FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
}
</div>
Se a coleção contiver milhares de voos, renderizar os voos levará muito tempo e os
usuários enfrentarão um atraso perceptível na interface do usuário. A maioria dos voos
não é vista porque eles ficam fora da altura do <div> elemento.

Em vez de renderizar toda a lista de voos de uma só vez, substitua o foreach loop no
exemplo anterior pelo Virtualize componente :

Especifique allFlights como uma origem de item fixa para


Virtualize<TItem>.Items. Somente os voos visíveis no momento são renderizados
pelo Virtualize componente .
Especifique um contexto para cada versão de pré-lançamento com o Context
parâmetro . No exemplo a seguir, flight é usado como o contexto , que fornece
acesso aos membros de cada voo.

razor

<div style="height:500px;overflow-y:scroll">
<Virtualize Items="@allFlights" Context="flight">
<FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
</Virtualize>
</div>

Se um contexto não for especificado com o Context parâmetro , use o valor de no


modelo de context conteúdo do item para acessar os membros de cada versão de pré-
lançamento:

razor

<div style="height:500px;overflow-y:scroll">
<Virtualize Items="@allFlights">
<FlightSummary @key="context.FlightId" Details="@context.Summary" />
</Virtualize>
</div>

O Virtualize componente:

Calcula o número de itens a serem renderizados com base na altura do contêiner e


no tamanho dos itens renderizados.
Recalcula e remete os itens à medida que o usuário rola.
Busca apenas a fatia de registros de uma API externa que corresponde à região
visível atual, em vez de baixar todos os dados da coleção.
Recebe um genérico ICollection<T> para Virtualize<TItem>.Items. Se uma coleção
não genérica fornecer os itens (por exemplo, uma coleção de DataRow), siga as
diretrizes na seção Delegado do provedor de itens para fornecer os itens.
O conteúdo do item para o Virtualize componente pode incluir:

HTML sem formatação e Razor código, como mostra o exemplo anterior.


Um ou mais Razor componentes.
Uma combinação de HTML/Razor e Razor componentes.

Delegado do provedor de itens


Se você não quiser carregar todos os itens na memória ou se a coleção não for genérica
ICollection<T>, você poderá especificar um método delegado do provedor de itens
para o parâmetro do Virtualize<TItem>.ItemsProvider componente que recupera de
forma assíncrona os itens solicitados sob demanda. No exemplo a seguir, o
LoadEmployees método fornece os itens para o Virtualize componente:

razor

<Virtualize Context="employee" ItemsProvider="@LoadEmployees">


<p>
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
</p>
</Virtualize>

O provedor de itens recebe um ItemsProviderRequest, que especifica o número


necessário de itens começando em um índice inicial específico. Em seguida, o provedor
de itens recupera os itens solicitados de um banco de dados ou outro serviço e os
retorna como um ItemsProviderResult<TItem> , juntamente com uma contagem do
total de itens. O provedor de itens pode optar por recuperar os itens com cada
solicitação ou armazená-los em cache para que eles estejam prontamente disponíveis.

Um Virtualize componente só pode aceitar uma fonte de item de seus parâmetros,


portanto, não tente usar simultaneamente um provedor de itens e atribuir uma coleção
a Items . Se ambos forem atribuídos, um InvalidOperationException será gerado quando
os parâmetros do componente forem definidos em runtime.

O exemplo a seguir carrega funcionários de um EmployeeService (não mostrado):

C#

private async ValueTask<ItemsProviderResult<Employee>> LoadEmployees(


ItemsProviderRequest request)
{
var numEmployees = Math.Min(request.Count, totalEmployees -
request.StartIndex);
var employees = await
EmployeesService.GetEmployeesAsync(request.StartIndex,
numEmployees, request.CancellationToken);

return new ItemsProviderResult<Employee>(employees, totalEmployees);


}

No exemplo a seguir, uma coleção de DataRow é uma coleção não genérica, portanto,
um delegado do provedor de itens é usado para virtualização:

razor

<Virtualize Context="row" ItemsProvider="@GetRows">


...
</Virtualize>

@code{
...

private ValueTask<ItemsProviderResult<DataRow>>
GetRows(ItemsProviderRequest request)
{
return new(new ItemsProviderResult<DataRow>(
dataTable.Rows.OfType<DataRow>
().Skip(request.StartIndex).Take(request.Count),
dataTable.Rows.Count));
}
}

Virtualize<TItem>.RefreshDataAsync instrui o componente a rerequestar dados de seu


ItemsProvider. Isso é útil quando os dados externos são alterados. Geralmente, não é
necessário chamar RefreshDataAsync ao usar Items.

RefreshDataAsync atualiza os dados de um Virtualize componente sem causar uma


nova geração. Se RefreshDataAsync for invocado de um manipulador de eventos ou de
um Blazor método de ciclo de vida do componente, disparar uma renderização não será
necessário porque uma renderização é disparada automaticamente no final do
manipulador de eventos ou do método de ciclo de vida. Se RefreshDataAsync for
disparado separadamente de uma tarefa ou evento em segundo plano, como no
delegado a seguir ForcecastUpdated , chame StateHasChanged para atualizar a interface
do usuário no final da tarefa ou evento em segundo plano:

C#

<Virtualize ... @ref="virtualizeComponent">


...
</Virtualize>

...
private Virtualize<FetchData>? virtualizeComponent;

protected override void OnInitialized()


{
WeatherForecastSource.ForcecastUpdated += async () =>
{
await InvokeAsync(async () =>
{
await virtualizeComponent?.RefreshDataAsync();
StateHasChanged();
});
});
}

No exemplo anterior:

RefreshDataAsync é chamado primeiro para obter novos dados para o Virtualize


componente.
StateHasChanged é chamado para gerar novamente o componente.

Espaço reservado
Como solicitar itens de uma fonte de dados remota pode levar algum tempo, você tem
a opção de renderizar um espaço reservado com o conteúdo do item:

Use um Placeholder ( <Placeholder>...</Placeholder> ) para exibir conteúdo até


que os dados do item sejam disponibilizados.
Use Virtualize<TItem>.ItemContent para definir o modelo de item para a lista.

razor

<Virtualize Context="employee" ItemsProvider="@LoadEmployees">


<ItemContent>
<p>
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
</p>
</ItemContent>
<Placeholder>
<p>
Loading&hellip;
</p>
</Placeholder>
</Virtualize>
Tamanho do item
A altura de cada item em pixels pode ser definida com Virtualize<TItem>.ItemSize
(padrão: 50). O exemplo a seguir altera a altura de cada item do padrão de 50 pixels
para 25 pixels:

razor

<Virtualize Context="employee" Items="@employees" ItemSize="25">


...
</Virtualize>

Por padrão, o Virtualize componente mede o tamanho de renderização (altura) de


itens individuais após a renderização inicial. Use ItemSize para fornecer um tamanho
exato de item com antecedência para ajudar no desempenho de renderização inicial
preciso e para garantir a posição de rolagem correta para recarregamentos de página.
Se o padrão ItemSize fizer com que alguns itens sejam renderizados fora da exibição
visível no momento, uma segunda renderização será disparada. Para manter
corretamente a posição de rolagem do navegador em uma lista virtualizada, a
renderização inicial deve estar correta. Caso contrário, os usuários poderão exibir os
itens errados.

Contagem de overscan
Virtualize<TItem>.OverscanCount determina quantos itens adicionais são renderizados
antes e depois da região visível. Essa configuração ajuda a reduzir a frequência de
renderização durante a rolagem. No entanto, valores mais altos resultam em mais
elementos renderizados na página (padrão: 3). O exemplo a seguir altera a contagem de
overscan do padrão de três itens para quatro itens:

razor

<Virtualize Context="employee" Items="@employees" OverscanCount="4">


...
</Virtualize>

Alterações de estado
Ao fazer alterações em itens renderizados pelo Virtualize componente, chame
StateHasChanged para forçar a reavaliação e a rerender do componente. Para saber
mais, consulte Renderização de componentes Razor no ASP.NET Core.
Suporte à rolagem de teclado
Para permitir que os usuários rolem o conteúdo virtualizado usando o teclado, verifique
se os elementos virtualizados ou o próprio contêiner de rolagem são focalizáveis. Se
você não executar essa etapa, a rolagem de teclado não funcionará em navegadores
baseados em Chromium.

Por exemplo, você pode usar um tabindex atributo no contêiner de rolagem:

razor

<div style="height:500px; overflow-y:scroll" tabindex="-1">


<Virtualize Items="@allFlights">
<div class="flight-info">...</div>
</Virtualize>
</div>

Para saber mais sobre o significado do tabindex valor -1 , 0 ou outros valores, consulte
tabindex (documentação do MDN) .

Estilos avançados e detecção de rolagem


O Virtualize componente foi projetado apenas para dar suporte a mecanismos de
layout de elemento específicos. Para entender quais layouts de elemento funcionam
corretamente, o seguinte explica como Virtualize detecta quais elementos devem ser
visíveis para exibição no local correto.

Se o código-fonte for semelhante ao seguinte:

razor

<div style="height:500px; overflow-y:scroll" tabindex="-1">


<Virtualize Items="@allFlights" ItemSize="100">
<div class="flight-info">Flight @context.Id</div>
</Virtualize>
</div>

No runtime, o Virtualize componente renderiza uma estrutura DOM semelhante à


seguinte:

HTML

<div style="height:500px; overflow-y:scroll" tabindex="-1">


<div style="height:1100px"></div>
<div class="flight-info">Flight 12</div>
<div class="flight-info">Flight 13</div>
<div class="flight-info">Flight 14</div>
<div class="flight-info">Flight 15</div>
<div class="flight-info">Flight 16</div>
<div style="height:3400px"></div>
</div>

O número real de linhas renderizadas e o tamanho dos espaçadores variam de acordo


com o estilo e Items o tamanho da coleção. No entanto, observe que há elementos
espaçadores div injetados antes e depois do conteúdo. Estes servem a duas finalidades:

Para fornecer um deslocamento antes e depois do conteúdo, fazendo com que os


itens visíveis no momento apareçam no local correto no intervalo de rolagem e no
próprio intervalo de rolagem para representar o tamanho total de todo o
conteúdo.
Para detectar quando o usuário está rolando além do intervalo visível atual, o que
significa que diferentes conteúdos devem ser renderizados.

7 Observação

Para saber como controlar a marca de elemento HTML do espaçador, consulte a


seção Controlar o nome da marca de elemento do espaçador mais adiante neste
artigo.

Os elementos do espaçador usam internamente um Observador de Interseção para


receber notificação quando estão ficando visíveis. Virtualize depende do recebimento
desses eventos. Virtualize funciona sob as seguintes condições:

Todos os itens de conteúdo têm altura idêntica. Isso possibilita calcular qual
conteúdo corresponde a uma determinada posição de rolagem sem primeiro
buscar cada item de dados e renderizar os dados em um elemento DOM.

Os espaçadores e as linhas de conteúdo são renderizados em uma única pilha


vertical com cada item preenchendo toda a largura horizontal. Geralmente, esse
é o padrão. Em casos típicos com div elementos, Virtualize funciona por padrão.
Se você estiver usando o CSS para criar um layout mais avançado, tenha em mente
os seguintes requisitos:
O estilo de contêiner de rolagem requer um com qualquer um display dos
seguintes valores:
block (o padrão para um div ).

table-row-group (o padrão para um tbody ).


flex com definido como flex-direction column . Verifique se os filhos

imediatos do Virtualize componente não são reduzidos sob regras flexíveis.


Por exemplo, adicione .mycontainer > div { flex-shrink: 0 } .
O estilo de linha de conteúdo requer um display com um dos seguintes
valores:
block (o padrão para um div ).

table-row (o padrão para um tr ).


Não use o CSS para interferir no layout dos elementos do espaçador. Por
padrão, os elementos do espaçador têm um display valor de block , exceto se
o pai for um grupo de linhas de tabela, nesse caso, eles usarão como padrão
table-row . Não tente influenciar a largura ou a altura do elemento espaçador,

inclusive fazendo com que eles tenham uma borda ou content pseudo-
elementos.

Qualquer abordagem que impeça que os espaçadores e elementos de conteúdo sejam


renderizados como uma única pilha vertical ou faça com que os itens de conteúdo
variem de altura, impede o funcionamento correto do Virtualize componente.

Virtualização no nível raiz


O Virtualize componente dá suporte ao uso do documento em si como a raiz de
rolagem, como uma alternativa para ter algum outro elemento com overflow-y:
scroll . No exemplo a seguir, os <html> elementos ou <body> são estilizados em um

componente com overflow-y: scroll :

razor

<HeadContent>
<style>
html, body { overflow-y: scroll }
</style>
</HeadContent>

Controlar o nome da marca de elemento do


espaçador
Se o Virtualize componente for colocado dentro de um elemento que requer um
nome de marca filho específico, SpacerElement permitirá que você obtenha ou defina o
nome da marca do espaçador de virtualização. O valor padrão é div . Para o exemplo a
seguir, o Virtualize componente é renderizado dentro de um elemento de corpo de
tabela (tbody ), de modo que o elemento filho apropriado para uma linha de tabela
(tr ) é definido como o espaçador.

Pages/VirtualizedTable.razor :

razor

@page "/virtualized-table"

<HeadContent>
<style>
html, body { overflow-y: scroll }
</style>
</HeadContent>

<h1>Virtualized Table Example</h1>

<table id="virtualized-table">
<thead style="position: sticky; top: 0; background-color: silver">
<tr>
<th>Item</th>
<th>Another column</th>
</tr>
</thead>
<tbody>
<Virtualize Items="@fixedItems" ItemSize="30" SpacerElement="tr">
<tr @key="context" style="height: 30px;" id="row-@context">
<td>Item @context</td>
<td>Another value</td>
</tr>
</Virtualize>
</tbody>
</table>

@code {
private List<int> fixedItems = Enumerable.Range(0, 1000).ToList();
}

No exemplo anterior, a raiz do documento é usada como o contêiner de rolagem, de


modo que os html elementos e body são estilizados com overflow-y: scroll . Para
saber mais, consulte os recursos a seguir:

Seção de virtualização no nível raiz


Controlar o conteúdo principal em aplicativos ASP.NET Core Blazor
Razor renderização de componente
ASP.NET Core
Artigo • 31/12/2022 • 30 minutos para o fim da leitura

Este artigo explica a renderização de componentes Razor em aplicativos ASP.NET


CoreBlazor, incluindo quando chamar StateHasChanged para disparar manualmente um
componente a ser renderizado.

Os componentes devem ser renderizados quando são adicionados pela primeira vez à
hierarquia de componentes por um componente pai. Esta é a única vez que um
componente deve renderizar. Os componentes podem ser renderizados em outros
momentos de acordo com sua própria lógica e convenções.

Convenções de renderização para


ComponentBase
Por padrão, Razor os componentes herdam da ComponentBase classe base, que contém
lógica para disparar a rerendering nos seguintes horários:

Depois de aplicar um conjunto atualizado de parâmetros de um componente pai.


Depois de aplicar um valor atualizado para um parâmetro em cascata.
Após a notificação de um evento e invocação de um de seus próprios
manipuladores de eventos.
Após uma chamada para seu próprio StateHasChanged método (consulte ASP.NET
Core Razor ciclo de vida do componente). Para obter diretrizes sobre como
impedir a substituição de parâmetros de componente filho quando
StateHasChanged é chamado em um componente pai, consulte ASP.NET Core
Razor componentes.

Componentes herdados de ComponentBase remetentes ignorados devido a


atualizações de parâmetro se um dos seguintes componentes for verdadeiro:

Todos os parâmetros são de um conjunto de tipos conhecidos† ou qualquer tipo


primitivo que não tenha sido alterado desde que o conjunto anterior de
parâmetros foi definido.

†A Blazor estrutura usa um conjunto de regras internas e verificações explícitas de


tipo de parâmetro para detecção de alterações. Essas regras e os tipos estão
sujeitos a alterações a qualquer momento. Para obter mais informações, consulte a
ChangeDetection API na fonte de referência ASP.NET Core .
7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

O método do ShouldRender componente retorna false .

Controlar o fluxo de renderização


Na maioria dos casos, ComponentBase as convenções resultam no subconjunto correto
de remetentes de componente após a ocorrência de um evento. Os desenvolvedores
geralmente não são obrigados a fornecer lógica manual para informar à estrutura quais
componentes gerar novamente e quando rerender. O efeito geral das convenções da
estrutura é que o componente que recebe um evento remete a si mesmo, o que dispara
rerender de forma recursiva de componentes descendentes cujos valores de parâmetro
podem ter sido alterados.

Para obter mais informações sobre as implicações de desempenho das convenções da


estrutura e como otimizar a hierarquia de componentes de um aplicativo para
renderização, consulte ASP.NET Core Blazor práticas recomendadas de desempenho.

Suprimir atualização da interface do usuário


( ShouldRender )
ShouldRender é chamado sempre que um componente é renderizado. Substitua
ShouldRender para gerenciar a atualização da interface do usuário. Se a implementação
retornar true , a interface do usuário será atualizada.

Mesmo que ShouldRender seja substituído, o componente é sempre renderizado


inicialmente.

Pages/ControlRender.razor :

razor

@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
<button @onclick="IncrementCount">Click me</button>
</p>

@code {
private int currentCount = 0;
private bool shouldRender = true;

protected override bool ShouldRender()


{
return shouldRender;
}

private void IncrementCount()


{
currentCount++;
}
}

Para obter mais informações sobre as práticas recomendadas de desempenho relativas


ao ShouldRender, consulte ASP.NET Core Blazor práticas recomendadas de
desempenho.

Quando chamar StateHasChanged


Chamar StateHasChanged permite disparar uma renderização a qualquer momento. No
entanto, tenha cuidado para não chamar StateHasChanged desnecessariamente, o que é
um erro comum que impõe custos de renderização desnecessários.

O código não deve precisar chamar StateHasChanged quando:

Manipulação rotineira de eventos, seja de forma síncrona ou assíncrona, já que


ComponentBase dispara uma renderização para a maioria dos manipuladores de
eventos de rotina.
Implementar a lógica típica do ciclo de vida, como OnInitialized ou
OnParametersSetAsync, seja de forma síncrona ou assíncrona, uma vez que
ComponentBase dispara uma renderização para eventos típicos do ciclo de vida.

No entanto, pode fazer sentido chamar StateHasChanged nos casos descritos nas
seções a seguir deste artigo:
Um manipulador assíncrono envolve várias fases assíncronas
Recebendo uma chamada de algo externo para o Blazor sistema de renderização e
tratamento de eventos
Para renderizar o componente fora da subárvore que é rerender por um evento
específico

Um manipulador assíncrono envolve várias fases


assíncronas
Devido à maneira como as tarefas são definidas no .NET, um receptor de um Task só
pode observar sua conclusão final, não estados assíncronos intermediários. Portanto,
ComponentBase só pode disparar a rerendering quando o Task é retornado pela
primeira vez e quando o Task finalmente é concluído. A estrutura não pode saber para
gerar novamente um componente em outros pontos intermediários, como quando um
IAsyncEnumerable<T>retorna dados em uma série de s intermediáriosTask . Se você
quiser gerar novamente em pontos intermediários, chame StateHasChanged nesses
pontos.

Considere o seguinte CounterState1 componente, que atualiza a contagem quatro


vezes em cada clique:

As renderizações automáticas ocorrem após os primeiros e últimos incrementos de


currentCount .

As renderizações manuais são disparadas por chamadas para StateHasChanged


quando a estrutura não dispara remetentes automaticamente em pontos de
processamento intermediários em que currentCount é incrementado.

Pages/CounterState1.razor :

razor

@page "/counter-state-1"

<p>
Current count: @currentCount
</p>

<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click
me</button>
</p>

@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically

await Task.Delay(1000);
currentCount++;
StateHasChanged();

await Task.Delay(1000);
currentCount++;
StateHasChanged();

await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}

Recebendo uma chamada de algo externo para o Blazor


sistema de renderização e tratamento de eventos
ComponentBase só sabe sobre seus próprios métodos de ciclo de vida e Blazoreventos
disparados. ComponentBase não sabe sobre outros eventos que podem ocorrer no
código. Por exemplo, todos os eventos C# gerados por um armazenamento de dados
personalizado são desconhecidos para Blazor. Para que esses eventos disparem a
rerendering para exibir valores atualizados na interface do usuário, chame
StateHasChanged.

Considere o seguinte CounterState2 componente que usa System.Timers.Timer para


atualizar uma contagem em um intervalo regular e chama StateHasChanged para
atualizar a interface do usuário:

OnTimerCallback é executado fora de qualquer Blazorfluxo de renderização


gerenciado ou notificação de evento. Portanto, OnTimerCallback deve chamar
StateHasChanged porque Blazor não está ciente das alterações currentCount no
retorno de chamada.
O componente implementa IDisposable, em que o Timer é descartado quando a
estrutura chama o Dispose método . Para saber mais, consulte Ciclo de vida de
renderização de Razor no ASP.NET Core.

Como o retorno de chamada é invocado fora do contexto de Blazorsincronização do , o


componente deve encapsular a lógica de OnTimerCallback em
ComponentBase.InvokeAsync para movê-lo para o contexto de sincronização do
renderizador. Isso é equivalente ao marshalling para o thread da interface do usuário
em outras estruturas de interface do usuário. StateHasChanged só pode ser chamado
do contexto de sincronização do renderizador e gera uma exceção caso contrário:

System.InvalidOperationException: 'O thread atual não está associado ao Dispatcher.


Use InvokeAsync() para alternar a execução para o Dispatcher ao disparar o estado
de renderização ou componente.

Pages/CounterState2.razor :

razor

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
Current count: @currentCount
</p>

@code {
private int currentCount = 0;
private Timer timer = new(1000);

protected override void OnInitialized()


{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}

private void OnTimerCallback()


{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}

public void Dispose() => timer.Dispose();


}

Para renderizar um componente fora da subárvore que é


rerender por um evento específico
A interface do usuário pode envolver:
1. Expedindo um evento para um componente.
2. Alterando algum estado.
3. Rerender um componente completamente diferente que não é descendente do
componente que recebe o evento.

Uma maneira de lidar com esse cenário é fornecer uma classe de gerenciamento de
estado , geralmente como um serviço de DI (injeção de dependência), injetada em vários
componentes. Quando um componente chama um método no gerenciador de estado, o
gerenciador de estado gera um evento C# que é recebido por um componente
independente.

Para obter abordagens para gerenciar o estado, consulte os seguintes recursos:

Seção Serviço de contêiner de estado na memória (Blazor Server) (Blazor


WebAssembly equivalente) do artigo Gerenciamento de estado.
Passe dados em uma hierarquia de componentes usando valores e parâmetros em
cascata.
Associe mais de dois componentes usando associações de dados.

Para a abordagem do gerenciador de estado, os eventos C# estão fora do Blazor


pipeline de renderização. Chame StateHasChanged em outros componentes que você
deseja gerar novamente em resposta aos eventos do gerenciador de estado.

A abordagem do gerenciador de estado é semelhante ao caso anterior com


System.Timers.Timer na seção anterior. Como a pilha de chamadas de execução
normalmente permanece no contexto de sincronização do renderizador, a chamada
InvokeAsync normalmente não é necessária. A chamada InvokeAsync só será necessária
se a lógica escapar do contexto de sincronização, como chamar ContinueWith em um
Task ou aguardar um Task com ConfigureAwait(false). Para obter mais informações,
consulte a seção Recebendo uma chamada de algo externo para o Blazor sistema de
renderização e tratamento de eventos .
Componentes com modelo Blazor no
ASP.NET Core
Artigo • 28/11/2022 • 15 minutos para o fim da leitura

Este artigo explica como os componentes modelo podem aceitar um ou mais modelos
de interface do usuário como parâmetros, que podem ser usados como parte da lógica
de renderização do componente.

Componentes modelo são componentes que aceitam um ou mais modelos de interface


do usuário como parâmetros, que podem ser usados como parte da lógica de
renderização do componente. Os componentes modelo permitem criar componentes
de nível superior que são mais reutilizáveis do que os componentes regulares. Alguns
exemplos incluem:

Um componente de tabela que permite que um usuário especifique modelos para


o cabeçalho, linhas e rodapé da tabela.
Um componente de lista que permite que um usuário especifique um modelo para
renderizar itens em uma lista.

Um componente modelo é definido especificando um ou mais parâmetros de


componente do tipo RenderFragment ou RenderFragment<TValue>. Um fragmento de
renderização representa um segmento da interface do usuário a ser renderizado.
RenderFragment<TValue> usa um parâmetro de tipo que pode ser especificado quando
o fragmento de renderização é invocado.

7 Observação

Para obter mais informações sobre RenderFragment, consulte ASP.NET Core Razor
componentes.

Geralmente, os componentes modelo são tipado genericamente, como demonstra o


componente a seguir TableTemplate . O tipo <T> genérico neste exemplo é usado para
renderizar IReadOnlyList<T> valores, que nesse caso é uma série de linhas de animais
de estimação em um componente que exibe uma tabela de animais de estimação.

Shared/TableTemplate.razor :

razor

@typeparam TItem
@using System.Diagnostics.CodeAnalysis
<table class="table">
<thead>
<tr>@TableHeader</tr>
</thead>
<tbody>
@foreach (var item in Items)
{
if (RowTemplate is not null)
{
<tr>@RowTemplate(item)</tr>
}
}
</tbody>
</table>

@code {
[Parameter]
public RenderFragment? TableHeader { get; set; }

[Parameter]
public RenderFragment<TItem>? RowTemplate { get; set; }

[Parameter, AllowNull]
public IReadOnlyList<TItem> Items { get; set; }
}

Ao usar um componente modelo, os parâmetros do modelo podem ser especificados


usando elementos filho que correspondem aos nomes dos parâmetros. No exemplo a
seguir, <TableHeader>...</TableHeader> e <RowTemplate>...<RowTemplate> forneça
RenderFragment<TValue> modelos para TableHeader e RowTemplate do TableTemplate
componente.

Especifique o Context atributo no elemento de componente quando quiser especificar


o nome do parâmetro de conteúdo para conteúdo filho implícito (sem nenhum
elemento filho de encapsulamento). No exemplo a seguir, o Context atributo aparece
no TableTemplate elemento e se aplica a todos os RenderFragment<TValue>
parâmetros de modelo.

Pages/Pets1.razor :

razor

@page "/pets1"

<h1>Pets</h1>

<TableTemplate Items="pets" Context="pet">


<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>

@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};

private class Pet


{
public int PetId { get; set; }
public string? Name { get; set; }
}
}

Como alternativa, você pode alterar o nome do parâmetro usando o Context atributo
no RenderFragment<TValue> elemento filho. No exemplo a seguir, o Context é
definido em RowTemplate em vez de TableTemplate :

Pages/Pets2.razor :

razor

@page "/pets2"

<h1>Pets</h1>

<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate Context="pet">
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>

@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};

private class Pet


{
public int PetId { get; set; }
public string? Name { get; set; }
}
}

Os argumentos de componente do tipo RenderFragment<TValue> têm um parâmetro


implícito chamado context , que pode ser usado. No exemplo a seguir, Context não
está definido. @context.{PROPERTY} fornece valores de animais de estimação para o
modelo, em que {PROPERTY} é uma Pet propriedade:

Pages/Pets3.razor :

razor

@page "/pets3"

<h1>Pets</h1>

<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>

@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};

private class Pet


{
public int PetId { get; set; }
public string? Name { get; set; }
}
}
Ao usar componentes de tipo genérico, o parâmetro de tipo é inferido, se possível. No
entanto, você pode especificar explicitamente o tipo com um atributo que tem um
nome que corresponda ao parâmetro de tipo, que está TItem no exemplo anterior:

Pages/Pets4.razor :

razor

@page "/pets4"

<h1>Pets</h1>

<TableTemplate Items="pets" TItem="Pet">


<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>

@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};

private class Pet


{
public int PetId { get; set; }
public string? Name { get; set; }
}
}

Recursos adicionais
Blazor práticas recomendadas de desempenho ASP.NET Core
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Blazor ASP.NET Core isolamento CSS
Artigo • 28/11/2022 • 14 minutos para o fim da leitura

Por Dave Brock

Este artigo explica como o isolamento do CSS define o CSS para Razor componentes, o
que pode simplificar o CSS e evitar colisões com outros componentes ou bibliotecas.

Isole estilos CSS em páginas, exibições e componentes individuais para reduzir ou evitar:

Dependências de estilos globais que podem ser desafiadoras de manter.


Conflitos de estilo em conteúdo aninhado.

Habilitar o isolamento CSS


Para definir estilos específicos do componente, crie um .razor.css arquivo que
corresponda ao .razor nome do arquivo para o componente na mesma pasta. O
.razor.css arquivo é um arquivo CSS com escopo.

Para um Example componente em um Example.razor arquivo, crie um arquivo ao lado


do componente chamado Example.razor.css . O Example.razor.css arquivo deve residir
na mesma pasta que o Example componente ( Example.razor ). O nome base " Example "
do arquivo não diferencia maiúsculas de minúsculas.

Pages/Example.razor :

razor

@page "/example"

<h1>Scoped CSS Example</h1>

Pages/Example.razor.css :

css

h1 {
color: brown;
font-family: Tahoma, Geneva, Verdana, sans-serif;
}
Os estilos definidos somente Example.razor.css são aplicados à saída renderizada do
Example componente. O isolamento CSS é aplicado a elementos HTML no arquivo
correspondente Razor . Quaisquer h1 declarações CSS definidas em outro lugar no
aplicativo não entram em conflito com os Example estilos do componente.

7 Observação

Para garantir o isolamento de estilo quando ocorre o agrupamento, não há suporte


para a importação de CSS em Razor blocos de código.

Agrupamento de isolamento CSS


O isolamento de CSS ocorre no momento do build. Blazor reescreve seletores CSS para
corresponder à marcação renderizada pelo componente. Os estilos CSS reescritos são
agrupados e produzidos como um ativo estático. A folha de estilos é referenciada
dentro da <head> marca (local do <head> conteúdo). O seguinte <link> elemento é
adicionado por padrão a um aplicativo criado a partir dos Blazor modelos de projeto,
em que o espaço reservado {ASSEMBLY NAME} é o nome do assembly do projeto:

HTML

<link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet">

O exemplo a seguir é de um aplicativo hospedado Blazor WebAssemblyClient . O nome


do assembly do aplicativo é BlazorSample.Client , e é <link> adicionado pelo Blazor
WebAssembly modelo de projeto quando o projeto é criado com a opção Hospedada
( -ho|--hosted opção usando a CLI do .NET ou ASP.NET Core caixa de seleção
hospedada usando o Visual Studio):

HTML

<link href="BlazorSample.Client.styles.css" rel="stylesheet">

Dentro do arquivo empacotado, cada componente está associado a um identificador de


escopo. Para cada componente estilizado, um atributo HTML é acrescentado com o
formato b-{STRING} , em que o {STRING} espaço reservado é uma cadeia de caracteres
de dez caracteres gerada pela estrutura. O identificador é exclusivo para cada aplicativo.
No componente renderizado Counter , Blazor acrescenta um identificador de escopo ao
h1 elemento:
HTML

<h1 b-3xxtam6d07>

O {ASSEMBLY NAME}.styles.css arquivo usa o identificador de escopo para agrupar uma


declaração de estilo com seu componente. O exemplo a seguir fornece o estilo do
elemento anterior <h1> :

css

/* /Pages/Counter.razor.rz.scp.css */
h1[b-3xxtam6d07] {
color: brown;
}

No momento da compilação, um pacote de projeto é criado com a convenção


obj/{CONFIGURATION}/{TARGET FRAMEWORK}/scopedcss/projectbundle/{ASSEMBLY
NAME}.bundle.scp.css , onde os espaços reservados são:

{CONFIGURATION} : a configuração de build do aplicativo (por exemplo, Debug , ).

Release
{TARGET FRAMEWORK} : a estrutura de destino (por exemplo, net6.0 ).

{ASSEMBLY NAME} : o nome do assembly do aplicativo (por exemplo, BlazorSample ).

Suporte a componente filho


Por padrão, o isolamento CSS só se aplica ao componente associado ao formato
{COMPONENT NAME}.razor.css , em que o espaço reservado {COMPONENT NAME} geralmente
é o nome do componente. Para aplicar alterações a um componente filho, use o
::deep pseudo-elemento para quaisquer elementos descendentes no arquivo do
.razor.css componente pai. O ::deep pseudo-elemento seleciona elementos

descendentes do identificador de escopo gerado por um elemento.

O exemplo a seguir mostra um componente pai chamado Parent com um componente


filho chamado Child .

Pages/Parent.razor :

razor

@page "/parent"

<div>
<h1>Parent component</h1>

<Child />
</div>

Shared/Child.razor :

razor

<h1>Child Component</h1>

Atualize a h1 declaração Parent.razor.css com o ::deep pseudo-elemento para


significar que a h1 declaração de estilo deve ser aplicada ao componente pai e seus
filhos.

Pages/Parent.razor.css :

css

::deep h1 {
color: red;
}

O h1 estilo agora se aplica aos Parent componentes e Child sem a necessidade de


criar um arquivo CSS com escopo separado para o componente filho.

O ::deep pseudo-elemento funciona apenas com elementos descendentes. A marcação


a seguir aplica os h1 estilos aos componentes conforme o esperado. O identificador de
escopo do componente pai é aplicado ao div elemento, portanto, o navegador sabe
herdar estilos do componente pai.

Pages/Parent.razor :

razor

<div>
<h1>Parent</h1>

<Child />
</div>

No entanto, excluir o div elemento remove a relação descendente. No exemplo a


seguir, o estilo não é aplicado ao componente filho.

Pages/Parent.razor :
razor

<h1>Parent</h1>

<Child />

O ::deep pseudo-elemento afeta onde o atributo de escopo é aplicado à regra. Quando


você define uma regra CSS em um arquivo CSS com escopo, o escopo é aplicado ao
elemento mais correto por padrão. Por exemplo: div > a é transformado em div >
a[b-{STRING}] , onde o {STRING} espaço reservado é uma cadeia de caracteres de dez
caracteres gerada pela estrutura (por exemplo, b-3xxtam6d07 ). Se você quiser que a
regra se aplique a um seletor diferente, o ::deep pseudo-elemento permitirá que você
faça isso. Por exemplo, div ::deep > a é transformado div[b-{STRING}] > a em (por
exemplo, div[b-3xxtam6d07] > a ).

A capacidade de anexar o ::deep pseudo-elemento a qualquer elemento HTML permite


que você crie estilos CSS com escopo que afetam elementos renderizados por outros
componentes quando você pode determinar a estrutura das marcas HTML renderizadas.
Para um componente que renderiza uma marca de hiperlink ( <a> ) dentro de outro
componente, verifique se o componente está encapsulado em um div (ou qualquer
outro elemento) e use a regra ::deep > a para criar um estilo que só é aplicado a esse
componente quando o componente pai for renderizado.

) Importante

O CSS no escopo só se aplica a elementos HTML e não a Razor componentes ou


auxiliares de marca, incluindo elementos com um Auxiliar de Marca aplicado, como
<input asp-for="..." /> .

Suporte para pré-processador de CSS


Pré-processadores de CSS são úteis para aprimorar o desenvolvimento de CSS
utilizando recursos como variáveis, aninhamento, módulos, mixins e herança. Embora o
isolamento CSS não dê suporte nativo a pré-processadores CSS, como Sass ou Less, a
integração de pré-processadores CSS é perfeita, desde que a compilação do pré-
processador ocorra antes Blazor de reescrever os seletores CSS durante o processo de
build. Usando o Visual Studio, por exemplo, configure a compilação de pré-processador
existente como uma tarefa Before Build no Gerenciador do Executor de Tarefas do
Visual Studio.
Muitos pacotes NuGet de terceiros, como Delegate.SassBuilder , podem compilar
arquivos SASS/SCSS no início do processo de build antes que o isolamento de CSS
ocorra e nenhuma configuração adicional seja necessária.

Configuração do isolamento de CSS


O isolamento de CSS foi projetado para funcionar pronto, mas fornece configuração
para alguns cenários avançados, como quando há dependências em ferramentas ou
fluxos de trabalho existentes.

Personalizar o formato de identificador de escopo


Por padrão, os identificadores de escopo usam o formato b-{STRING} , em que o espaço
reservado {STRING} é uma cadeia de caracteres de dez caracteres gerada pela estrutura.
Para personalizar o formato do identificador de escopo, atualize o arquivo de projeto
para um padrão desejado:

XML

<ItemGroup>
<None Update="Pages/Example.razor.css" CssScope="custom-scope-identifier"
/>
</ItemGroup>

No exemplo anterior, o CSS gerado para Example.razor.css altera o identificador de


escopo de b-{STRING} para custom-scope-identifier .

Use identificadores de escopo para obter a herança com arquivos CSS com escopo. No
exemplo de arquivo de projeto a seguir, um BaseComponent.razor.css arquivo contém
estilos comuns entre componentes. Um arquivo DerivedComponent.razor.css herda
esses estilos.

XML

<ItemGroup>
<None Update="Pages/BaseComponent.razor.css" CssScope="custom-scope-
identifier" />
<None Update="Pages/DerivedComponent.razor.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

Use o operador curinga ( * ) para compartilhar identificadores de escopo em vários


arquivos:
XML

<ItemGroup>
<None Update="Pages/*.razor.css" CssScope="custom-scope-identifier" />
</ItemGroup>

Alterar o caminho base para ativos da Web estáticos


O scoped.styles.css arquivo é gerado na raiz do aplicativo. No arquivo de projeto, use
a <StaticWebAssetBasePath> propriedade para alterar o caminho padrão. O exemplo a
_content seguir coloca o scoped.styles.css arquivo e o restante dos ativos do

aplicativo no caminho:

XML

<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

Desabilitar o agrupamento automático


Para recusar como Blazor publica e carrega arquivos com escopo no runtime, use a
DisableScopedCssBundling propriedade. Ao usar essa propriedade, isso significa que
outras ferramentas ou processos são responsáveis por tirar os arquivos CSS isolados do
obj diretório e publicá-los e carregá-los em runtime:

XML

<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Desabilitar o isolamento CSS


Desabilite o isolamento CSS para um projeto definindo a <ScopedCssEnabled>
propriedade false no arquivo de projeto do aplicativo:

XML

<ScopedCssEnabled>false</ScopedCssEnabled>
Suporte à RCL (biblioteca de classes) Razor
Estilos isolados para componentes em um pacote NuGet ou Razor uma RCL (biblioteca
de classes) são agrupados automaticamente:

O aplicativo usa importações de CSS para fazer referência aos estilos empacotados
da RCL. Para uma biblioteca de classes nomeada ClassLib e um Blazor aplicativo
com uma BlazorSample.styles.css folha de estilos, a folha de estilos da RCL é
importada na parte superior da folha de estilos do aplicativo:

css

@import '_content/ClassLib/ClassLib.bundle.scp.css';

Os estilos empacotados da RCL não são publicados como um ativo da Web


estático do aplicativo que consome os estilos.

Para obter mais informações sobre RCLs, consulte os seguintes artigos:

Consumir componentes do Razor de uma Razor biblioteca de classes (RCL)


Interface do usuário do Razor reutilizável em bibliotecas de classes com o ASP.NET
Core

Recursos adicionais
Razor Isolamento CSS de páginas
Isolamento de CSS do MVC
Componentes de ASP.NET Core Razor
renderizados dinamicamente
Artigo • 10/01/2023 • 14 minutos para o fim da leitura

Por Dave Brock

Use o componente interno DynamicComponent para renderizar componentes por tipo.

Um DynamicComponent é útil para renderizar componentes sem iterar por meio de


tipos possíveis ou usar lógica condicional. Por exemplo, DynamicComponent pode
renderizar um componente com base em uma seleção de usuário de uma lista suspensa.

No exemplo a seguir:

componentType especifica o tipo .

parameters especifica os parâmetros de componente a serem passados para o

componentType componente.

razor

<DynamicComponent Type="@componentType" Parameters="@parameters" />

@code {
private Type componentType = ...;
private IDictionary<string, object> parameters = ...;
}

Para obter mais informações sobre como passar valores de parâmetro, consulte a seção
Parâmetros de passagem mais adiante neste artigo.

Use a Instance propriedade para acessar a instância de componente criada


dinamicamente:

razor

<DynamicComponent Type="@typeof({COMPONENT})" @ref="dc" />

<button @onclick="Refresh">Refresh</button>

@code {
private DynamicComponent? dc;

private Task Refresh()


{
return (dc?.Instance as IRefreshable)?.Refresh();
}
}

No exemplo anterior:

O {COMPONENT} espaço reservado é o tipo de componente criado dinamicamente.


IRefreshable é uma interface de exemplo fornecida pelo desenvolvedor para a

instância de componente dinâmico.

Exemplo
No exemplo a seguir, um Razor componente renderiza um componente com base na
seleção do usuário de uma lista suspensa de quatro valores possíveis.

Seleção da operadora de voo espacial do Componente compartilhado Razor a ser


usuário renderizado

Laboratório® rocket Shared/RocketLab.razor

SpaceX® Shared/SpaceX.razor

ULA® Shared/UnitedLaunchAlliance.razor

Virgin Galactic® Shared/VirginGalactic.razor

Shared/RocketLab.razor :

razor

<h2>Rocket Lab®</h2>

<p>
Rocket Lab is a registered trademark of
<a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

Shared/SpaceX.razor :

razor

<h2>SpaceX®</h2>

<p>
SpaceX is a registered trademark of
<a href="https://www.spacex.com/">Space Exploration Technologies Corp.
</a>
</p>
Shared/UnitedLaunchAlliance.razor :

razor

<h2>United Launch Alliance®</h2>

<p>
United Launch Alliance and ULA are registered trademarks of
<a href="https://www.ulalaunch.com/">United Launch Alliance, LLC</a>.
</p>

Shared/VirginGalactic.razor :

razor

<h2>Virgin Galactic®</h2>

<p>
Virgin Galactic is a registered trademark of
<a href="https://www.virgingalactic.com/">Galactic Enterprises, LLC</a>.
</p>

Pages/DynamicComponentExample1.razor :

razor

@page "/dynamiccomponent-example-1"

<h1><code>DynamicComponent</code> Component Example 1</h1>

<p>
<label>
Select your transport:
<select @onchange="OnDropdownChange">
<option value="">Select a value</option>
<option value="@nameof(RocketLab)">Rocket Lab</option>
<option value="@nameof(SpaceX)">SpaceX</option>
<option value="@nameof(UnitedLaunchAlliance)">ULA</option>
<option value="@nameof(VirginGalactic)">Virgin Galactic</option>
</select>
</label>
</p>

@if (selectedType is not null)


{
<div class="border border-primary my-1 p-1">
<DynamicComponent Type="@selectedType" />
</div>
}
@code {
private Type? selectedType;

private void OnDropdownChange(ChangeEventArgs e)


{
selectedType = e.Value?.ToString()?.Length > 0 ?
Type.GetType($"BlazorSample.Shared.{e.Value}") : null;
}
}

No exemplo anterior:

Os nomes de componente são usados como valores de opção usando o nameof


operador , que retorna nomes de componente como cadeias de caracteres
constantes.
O namespace do aplicativo é BlazorSample . Altere o namespace para corresponder
ao namespace do aplicativo.

Passar parâmetros
Se os componentes renderizados dinamicamente tiverem parâmetros de componente,
passe-os para o DynamicComponent como um IDictionary<string, object> . O string
é o nome do parâmetro e o object é o valor do parâmetro.

O exemplo a seguir configura um objeto de metadados de componente


( ComponentMetadata ) para fornecer valores de parâmetro para componentes
renderizados dinamicamente com base no nome do tipo. O exemplo é apenas uma das
várias abordagens que você pode adotar. Os dados de parâmetro também podem ser
fornecidos de uma API Web, um banco de dados ou um método . O único requisito é
que a abordagem retorne um IDictionary<string, object> .

ComponentMetadata.cs :

C#

public class ComponentMetadata


{
public string? Name { get; set; }
public Dictionary<string, object> Parameters { get; set; } =
new Dictionary<string, object>();
}

O seguinte RocketLabWithWindowSeat componente


( Shared/RocketLabWithWindowSeat.razor ) foi atualizado do exemplo anterior para incluir
um parâmetro de componente chamado WindowSeat para especificar se o passageiro
prefere um assento de janela em seu voo:

Shared/RocketLabWithWindowSeat.razor :

razor

<h2>Rocket Lab®</h2>

<p>
User selected a window seat: @WindowSeat
</p>

<p>
Rocket Lab is a trademark of
<a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

@code {
[Parameter]
public bool WindowSeat { get; set; }
}

No exemplo a seguir:

Somente o RocketLabWithWindowSeat parâmetro do componente para um assento


de janela ( WindowSeat ) recebe o valor da caixa de Window Seat seleção.
O namespace do aplicativo é BlazorSample . Altere o namespace para corresponder
ao namespace do aplicativo.
Os componentes renderizados dinamicamente são componentes compartilhados
na pasta do Shared aplicativo:
Mostrado nesta seção de artigo: RocketLabWithWindowSeat
( Shared/RocketLabWithWindowSeat.razor )
Componentes mostrados na seção Exemplo anteriormente neste artigo:
SpaceX ( Shared/SpaceX.razor )

UnitedLaunchAlliance ( Shared/UnitedLaunchAlliance.razor )

VirginGalactic ( Shared/VirginGalactic.razor )

Pages/DynamicComponentExample2.razor :

razor

@page "/dynamiccomponent-example-2"

<h1><code>DynamicComponent</code> Component Example 2</h1>

<p>
<label>
<input type="checkbox" @bind="WindowSeat" />
Window Seat (Rocket Lab only)
</label>
</p>

<p>
<label>
Select your transport:
<select @onchange="OnDropdownChange">
<option value="">Select a value</option>
@foreach (var c in components)
{
<option value="@c.Key">@c.Value.Name</option>
}
</select>
</label>
</p>

@if (selectedType is not null)


{
<div class="border border-primary my-1 p-1">
<DynamicComponent Type="@selectedType"
Parameters="@components[selectedType.Name].Parameters" />
</div>
}

@code {
private Dictionary<string, ComponentMetadata> components =
new()
{
{
"RocketLabWithWindowSeat",
new ComponentMetadata
{
Name = "Rocket Lab with Window Seat",
Parameters = new() { { "WindowSeat", false } }
}
},
{
"VirginGalactic",
new ComponentMetadata { Name = "Virgin Galactic" }
},
{
"UnitedLaunchAlliance",
new ComponentMetadata { Name = "ULA" }
},
{
"SpaceX",
new ComponentMetadata { Name = "SpaceX" }
}
};
private Type? selectedType;
private bool windowSeat;
private bool WindowSeat
{
get { return windowSeat; }
set
{
windowSeat = value;

components[nameof(RocketLabWithWindowSeat)].Parameters["WindowSeat"] =
windowSeat;
}
}

private void OnDropdownChange(ChangeEventArgs e)


{
selectedType = e.Value?.ToString()?.Length > 0 ?
Type.GetType($"BlazorSample.Shared.{e.Value}") : null;
}
}

Retornos de chamada de evento


( EventCallback )
Os retornos de chamada de evento (EventCallback) podem ser passados para um
DynamicComponent em seu dicionário de parâmetros.

ComponentMetadata.cs :

C#

public class ComponentMetadata


{
public string? Name { get; set; }
public Dictionary<string, object> Parameters { get; set; } =
new Dictionary<string, object>();
}

Implemente um parâmetro de retorno de chamada de evento (EventCallback) em cada


componente renderizado dinamicamente.

Shared/RocketLab2.razor :

razor

<h2>Rocket Lab®</h2>

<p>
Rocket Lab is a registered trademark of
<a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>

@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

Shared/SpaceX2.razor :

razor

<h2>SpaceX®</h2>

<p>
SpaceX is a registered trademark of
<a href="https://www.spacex.com/">Space Exploration Technologies Corp.
</a>
</p>

<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>

@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

Shared/UnitedLaunchAlliance2.razor :

razor

<h2>United Launch Alliance®</h2>

<p>
United Launch Alliance and ULA are registered trademarks of
<a href="https://www.ulalaunch.com/">United Launch Alliance, LLC</a>.
</p>

<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>

@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
Shared/VirginGalactic2.razor :

razor

<h2>Virgin Galactic®</h2>

<p>
Virgin Galactic is a registered trademark of
<a href="https://www.virgingalactic.com/">Galactic Enterprises, LLC</a>.
</p>

<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>

@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

No exemplo de componente pai a seguir, o ShowDTMessage método atribui uma cadeia


de caracteres com a hora atual a message e o valor de message é renderizado.

O componente pai passa o método de retorno de chamada, ShowDTMessage no


dicionário de parâmetros:

A string chave é o nome do método de retorno de chamada, OnClickCallback .


O object valor é criado pelo EventCallbackFactory.Create para o método de
retorno de chamada pai, ShowDTMessage . Observe que a this palavra-chave não tem
suporte em campos C#, portanto, uma propriedade C# é usada para o dicionário
de parâmetros.

Para o componente a seguir, altere o nome do namespace do BlazorSample no


OnDropdownChange método para corresponder ao namespace do aplicativo.

Pages/DynamicComponentExample3.razor :

razor

@page "/dynamiccomponent-example-3"

<h1><code>DynamicComponent</code> Component Example 3</h1>

<p>
<label>
Select your transport:
<select @onchange="OnDropdownChange">
<option value="">Select a value</option>
<option value="@nameof(RocketLab2)">Rocket Lab</option>
<option value="@nameof(SpaceX2)">SpaceX</option>
<option value="@nameof(UnitedLaunchAlliance2)">ULA</option>
<option value="@nameof(VirginGalactic2)">Virgin
Galactic</option>
</select>
</label>
</p>

@if (selectedType is not null)


{
<div class="border border-primary my-1 p-1">
<DynamicComponent Type="@selectedType"
Parameters="@Components[selectedType.Name].Parameters" />
</div>
}

<p>
@message
</p>

@code {
private Type? selectedType;
private string? message;

private Dictionary<string, ComponentMetadata> Components


{
get
{
return new Dictionary<string, ComponentMetadata>()
{
{
"RocketLab2",
new ComponentMetadata
{
Name = "Rocket Lab",
Parameters =
new()
{
{
"OnClickCallback",

EventCallback.Factory.Create<MouseEventArgs>(
this, ShowDTMessage)
}
}
}
},
{
"VirginGalactic2",
new ComponentMetadata
{
Name = "Virgin Galactic",
Parameters =
new()
{
{
"OnClickCallback",

EventCallback.Factory.Create<MouseEventArgs>(
this, ShowDTMessage)
}
}
}
},
{
"UnitedLaunchAlliance2",
new ComponentMetadata
{
Name = "ULA",
Parameters =
new()
{
{
"OnClickCallback",

EventCallback.Factory.Create<MouseEventArgs>(
this, ShowDTMessage)
}
}
}
},
{
"SpaceX2",
new ComponentMetadata
{
Name = "SpaceX",
Parameters =
new()
{
{
"OnClickCallback",

EventCallback.Factory.Create<MouseEventArgs>(
this, ShowDTMessage)
}
}
}
}
};
}
}

private void OnDropdownChange(ChangeEventArgs e)


{
selectedType = e.Value?.ToString()?.Length > 0 ?
Type.GetType($"BlazorSample.Shared.{e.Value}") : null;
}

private void ShowDTMessage(MouseEventArgs e) =>


message = $"The current DT is: {DateTime.Now}.";
}

Evitar parâmetros catch-all


Evite o uso de parâmetros catch-all. Se parâmetros catch-all forem usados, cada
parâmetro explícito em DynamicComponent efetivamente será uma palavra reservada
que você não pode passar para um filho dinâmico. Todos os novos parâmetros passados
para DynamicComponent são uma alteração interruptiva, pois começam a sombrear
parâmetros de componente filho que têm o mesmo nome. É improvável que o
chamador sempre conheça um conjunto fixo de nomes de parâmetros para passar para
todos os filhos dinâmicos possíveis.

Marcas Comerciais
Rocket Lab é uma marca registrada da Rocket Lab USA Inc. A SpaceX é uma marca
registrada da Space Exploration Technologies Corp. United Launch Alliance e ULA são
marcas registradas da United Launch Alliance, LLC . Virgin Galactic é uma marca
registrada da Galactic Enterprises, LLC .

Recursos adicionais
Blazor manipulação de eventos ASP.NET Core
Pré-renderizar e integrar componentes
Razor do ASP.NET Core
Artigo • 21/12/2022 • 72 minutos para o fim da leitura

Este artigo explica os cenários Razor de integração de componentes para Blazor


aplicativos, incluindo a pré-geração de Razor componentes no servidor.

Razor os componentes podem ser integrados aos Razor aplicativos Pages e MVC.
Quando a página ou a exibição é renderizada, os componentes podem ser pré-gerados
ao mesmo tempo.

A pré-geração pode melhorar a SEO (Otimização do Mecanismo de Pesquisa)


renderizando conteúdo para a resposta HTTP inicial que os mecanismos de pesquisa
podem usar para calcular a classificação da página.

Depois de configurar o projeto, use as diretrizes nas seções a seguir, dependendo dos
requisitos do projeto:

Para componentes que são roteáveis diretamente de solicitações de usuário. Siga


estas diretrizes quando os visitantes devem ser capazes de fazer uma solicitação
HTTP em seu navegador para um componente com uma @page diretiva .
Usar componentes roteáveis em um Razor aplicativo Pages
Usar componentes roteáveis em um aplicativo MVC
Para componentes que não são roteáveis diretamente de solicitações de usuário,
consulte a seção Renderizar componentes de uma página ou exibição . Siga estas
diretrizes quando o aplicativo inserir componentes em páginas e exibições
existentes com o Auxiliar de Marca de Componente.

Configuração
Use as diretrizes a seguir para integrar Razor componentes em páginas e exibições de
um aplicativo Pages ou MVC existente Razor .

1. Adicione um arquivo de importações à pasta raiz do projeto com o conteúdo a


seguir. Altere o {APP NAMESPACE} espaço reservado para o namespace do projeto.

_Imports.razor :

razor

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using {APP NAMESPACE}

2. No arquivo de layout do projeto ( Pages/Shared/_Layout.cshtml em Razor


aplicativos pages ou Views/Shared/_Layout.cshtml em aplicativos MVC):

Adicione a seguinte <base> marca e HeadOutlet o auxiliar de marca de


componente ao <head> elemento :

CSHTML

<base href="~/" />


<component
type="typeof(Microsoft.AspNetCore.Components.Web.HeadOutlet)"
render-mode="ServerPrerendered" />

O href valor (o caminho base do aplicativo) no exemplo anterior pressupõe


que o aplicativo reside no caminho da URL raiz ( / ). Se o aplicativo for um
subaplicação, siga as diretrizes na seção Caminho base do aplicativo do
artigo Hospedar e implantar ASP.NET CoreBlazor.

O HeadOutlet componente é usado para renderizar o conteúdo de cabeçalho


( <head> ) para títulos de página (PageTitle componente) e outros elementos
de cabeçalho (HeadContent componente) definidos por Razor componentes.
Para obter mais informações, consulte Controlar o conteúdo principal em
aplicativos ASP.NET CoreBlazor.

Adicione uma <script> marca para o blazor.server.js script imediatamente


antes da Scripts seção renderização ( @await RenderSectionAsync(...) ):

HTML

<script src="_framework/blazor.server.js"></script>

A estrutura adiciona o blazor.server.js script ao aplicativo. Não é


necessário adicionar manualmente um blazor.server.js arquivo de script ao
aplicativo.

7 Observação
Normalmente, o layout é carregado por meio de um _ViewStart.cshtml
arquivo.

3. Registre os serviços em que Program.cs os Blazor Server serviços estão registrados:

C#

builder.Services.AddServerSideBlazor();

4. Adicione o ponto de extremidade hub Blazor aos pontos de extremidade de


Program.cs onde as rotas são mapeadas. Coloque a seguinte linha após a

chamada para MapRazorPages (Razor Pages) ou MapControllerRoute (MVC):

C#

app.MapBlazorHub();

5. Integrar componentes a qualquer página ou exibição. Por exemplo, adicione um


Counter componente à pasta do Shared projeto.

Pages/Shared/Counter.razor (Razor Pages) ou Views/Shared/Counter.razor (MVC):

razor

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click


me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Razor Páginas:

Na página do Index projeto de um Razor aplicativo Pages, adicione o Counter


namespace do componente e insira o componente na página. Quando a Index
página é carregada, o Counter componente é pré-gerado na página. No exemplo
a seguir, substitua o {APP NAMESPACE} espaço reservado pelo namespace do
projeto.

Pages/Index.cshtml :

CSHTML

@page
@using {APP NAMESPACE}.Pages.Shared
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

<component type="typeof(Counter)" render-mode="ServerPrerendered" />

MVC:

Na exibição do Index projeto de um aplicativo MVC, adicione o Counter


namespace do componente e insira o componente na exibição. Quando a exibição
Index é carregada, o Counter componente é pré-gerado na página. No exemplo a

seguir, substitua o {APP NAMESPACE} espaço reservado pelo namespace do projeto.

Views/Home/Index.cshtml :

CSHTML

@using {APP NAMESPACE}.Views.Shared


@{
ViewData["Title"] = "Home Page";
}

<component type="typeof(Counter)" render-mode="ServerPrerendered" />

Para obter mais informações, consulte a seção Renderizar componentes de uma página
ou exibição .

Usar componentes roteáveis em um Razor


aplicativo Pages
Esta seção diz respeito à adição de componentes que podem ser roteáveis diretamente de
solicitações de usuário.

Para dar suporte a componentes roteáveis Razor em Razor aplicativos pages:


1. Siga as diretrizes na seção Configuração .

2. Adicione um App componente à raiz do projeto com o conteúdo a seguir.

App.razor :

razor

@using Microsoft.AspNetCore.Components.Routing

<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<p role="alert">Sorry, there's nothing at this address.</p>
</NotFound>
</Router>

3. Adicione uma _Host página ao projeto com o conteúdo a seguir. Substitua o {APP
NAMESPACE} espaço reservado pelo namespace do aplicativo.

Pages/_Host.cshtml :

CSHTML

@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<component type="typeof(App)" render-mode="ServerPrerendered" />

7 Observação

O exemplo anterior pressupõe que o componente e Blazor o HeadOutlet


script ( _framework/blazor.server.js ) sejam renderizados pelo layout do
aplicativo. Para obter mais informações, consulte a seção Configuração .

RenderMode configura se o App componente:

É pré-gerado na página.
É renderizado como HTML estático na página ou se ele inclui as informações
necessárias para inicializar um Blazor aplicativo do agente do usuário.

Para obter mais informações sobre o Auxiliar de Marca de Componente, incluindo


a passagem de parâmetros e RenderMode a configuração, consulte Auxiliar de
marca de componente no ASP.NET Core.

4. Program.cs Nos pontos de extremidade, adicione uma rota de baixa prioridade


para a _Host página como o último ponto de extremidade:

C#

app.MapFallbackToPage("/_Host");

5. Adicione componentes roteáveis ao projeto. O exemplo a seguir é um


RoutableCounter componente baseado no Counter componente nos modelos de

Blazor projeto.

Pages/RoutableCounter.razor :

razor

@page "/routable-counter"

<PageTitle>Routable Counter</PageTitle>

<h1>Routable Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click


me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

6. Execute o projeto e navegue até o componente roteável RoutableCounter em


/routable-counter .

Para obter mais informações sobre namespaces, consulte a seção Namespaces de


componente .

Usar componentes roteáveis em um aplicativo


MVC
Esta seção refere-se à adição de componentes que são roteáveis diretamente de
solicitações do usuário.

Para dar suporte a componentes roteáveis Razor em aplicativos MVC:

1. Siga as diretrizes na seção Configuração .

2. Adicione um App componente à raiz do projeto com o conteúdo a seguir.

App.razor :

razor

@using Microsoft.AspNetCore.Components.Routing

<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<p role="alert">Sorry, there's nothing at this address.</p>
</NotFound>
</Router>

3. Adicione um modo _Host de exibição ao projeto com o conteúdo a seguir.


Substitua o {APP NAMESPACE} espaço reservado pelo namespace do aplicativo.

Views/Home/_Host.cshtml :

CSHTML

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<component type="typeof(App)" render-mode="ServerPrerendered" />

7 Observação

O exemplo anterior pressupõe que o componente e Blazor o HeadOutlet


script ( _framework/blazor.server.js ) são renderizados pelo layout do
aplicativo. Para obter mais informações, consulte a seção Configuração .

RenderMode configura se o App componente:

É pré-gerado na página.
É renderizado como HTML estático na página ou se inclui as informações
necessárias para inicializar um Blazor aplicativo do agente do usuário.

Para obter mais informações sobre o Auxiliar de Marca de Componente, incluindo


a passagem de parâmetros e RenderMode a configuração, consulte Auxiliar de
marca de componente no ASP.NET Core.

4. Adicione uma ação ao Home controlador.

Controllers/HomeController.cs :

C#

public IActionResult Blazor()


{
return View("_Host");
}

5. Program.cs Nos pontos de extremidade, adicione uma rota de baixa prioridade


para a ação do controlador que retorna a _Host exibição:

C#

app.MapFallbackToController("Blazor", "Home");

6. Crie uma Pages pasta no aplicativo MVC e adicione componentes roteáveis. O


exemplo a seguir é um RoutableCounter componente baseado no Counter
componente nos modelos de Blazor projeto.

Pages/RoutableCounter.razor :

razor

@page "/routable-counter"

<PageTitle>Routable Counter</PageTitle>

<h1>Routable Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click


me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

7. Execute o projeto e navegue até o componente roteável RoutableCounter em


/routable-counter .

Para obter mais informações sobre namespaces, consulte a seção Namespaces de


componente .

Renderizar componentes de uma página ou


exibição
Esta seção refere-se à adição de componentes a páginas ou exibições, em que os
componentes não são roteáveis diretamente das solicitações do usuário.

Para renderizar um componente de uma página ou exibição, use o Auxiliar de Marca de


Componente.

Renderizar componentes interativos com estado


Componentes interativos com estado podem ser adicionados a uma Razor página ou
exibição.

Quando a página ou exibição é renderizada:

O componente é pré-gerado com a página ou exibição.


O estado inicial do componente usado para pré-geração é perdido.
O novo estado do componente é criado quando a SignalR conexão é estabelecida.

A página a seguir Razor renderiza um Counter componente:

CSHTML

<h1>Razor Page</h1>

<component type="typeof(Counter)" render-mode="ServerPrerendered"


param-InitialValue="InitialValue" />

@functions {
[BindProperty(SupportsGet=true)]
public int InitialValue { get; set; }
}
Para obter mais informações, consulte Auxiliar de marca de componente em ASP.NET
Core.

Renderizar componentes não interativos


Na página a seguir Razor , o Counter componente é renderizado estaticamente com um
valor inicial especificado usando um formulário. Como o componente é renderizado
estaticamente, o componente não é interativo:

CSHTML

<h1>Razor Page</h1>

<form>
<input type="number" asp-for="InitialValue" />
<button type="submit">Set initial value</button>
</form>

<component type="typeof(Counter)" render-mode="Static"


param-InitialValue="InitialValue" />

@functions {
[BindProperty(SupportsGet=true)]
public int InitialValue { get; set; }
}

Para obter mais informações, consulte Auxiliar de marca de componente em ASP.NET


Core.

Namespaces de componente
Ao usar uma pasta personalizada para manter os componentes do Razor projeto,
adicione o namespace que representa a pasta à página/exibição ou ao
_ViewImports.cshtml arquivo. No exemplo a seguir:

Os componentes são armazenados na Components pasta do projeto.


O {APP NAMESPACE} espaço reservado é o namespace do projeto. Components
representa o nome da pasta.

CSHTML

@using {APP NAMESPACE}.Components


O _ViewImports.cshtml arquivo está localizado na Pages pasta de um Razor aplicativo
Pages ou na Views pasta de um aplicativo MVC.

Para obter mais informações, consulte componentes ASP.NET CoreRazor.

Manter o estado pré-gerado


Sem persistir o estado pré-gerado, o estado usado durante a pré-geração é perdido e
deve ser recriado quando o aplicativo é totalmente carregado. Se algum estado for
configurado de forma assíncrona, a interface do usuário poderá piscar à medida que a
interface do usuário pré-gerada for substituída por espaços reservados temporários e,
em seguida, totalmente renderizada novamente.

Para resolver esses problemas, Blazor o dá suporte ao estado persistente em uma


página pré-gerada usando o Auxiliar de Marca de Estado do Componente Persistente.
Adicione a marca do Auxiliar de Marca, <persist-component-state /> , dentro da marca
de fechamento </body> .

Pages/_Host.cshtml :

CSHTML

<body>
...

<persist-component-state />
</body>

Decida qual estado persistir usando o PersistentComponentState serviço.


PersistentComponentState.RegisterOnPersisting registra um retorno de chamada para
persistir o estado do componente antes que o aplicativo seja pausado. O estado é
recuperado quando o aplicativo é retomado.

O exemplo a seguir é uma versão atualizada do FetchData componente em um


aplicativo hospedado Blazor WebAssembly com base no modelo de Blazor projeto. O
WeatherForecastPreserveState componente persiste o estado de previsão do tempo
durante a pré-geração e recupera o estado para inicializar o componente. O Auxiliar de
Marca de Estado do Componente Persistente mantém o estado do componente após
todas as invocações de componente.

Pages/WeatherForecastPreserveState.razor :

razor
@page "/weather-forecast-preserve-state"
@implements IDisposable
@using BlazorSample.Shared
@inject IWeatherForecastService WeatherForecastService
@inject PersistentComponentState ApplicationState

<PageTitle>Weather Forecast</PageTitle>

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)


{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}

@code {
private WeatherForecast[] forecasts = Array.Empty<WeatherForecast>();
private PersistingComponentStateSubscription persistingSubscription;

protected override async Task OnInitializedAsync()


{
persistingSubscription =
ApplicationState.RegisterOnPersisting(PersistForecasts);

if (!ApplicationState.TryTakeFromJson<WeatherForecast[]>(
"fetchdata", out var restored))
{
forecasts =
await
WeatherForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now))
;
}
else
{
forecasts = restored!;
}
}

private Task PersistForecasts()


{
ApplicationState.PersistAsJson("fetchdata", forecasts);

return Task.CompletedTask;
}

void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
}

Ao inicializar componentes com o mesmo estado usado durante a pré-geração, todas as


etapas de inicialização caras são executadas apenas uma vez. A interface do usuário
renderizada também corresponde à interface do usuário pré-gerada, portanto,
nenhuma cintilação ocorre no navegador.

Tamanho do estado pré-gerado e SignalR


limite de tamanho da mensagem
Um tamanho de estado pré-gerado grande pode exceder o SignalR limite de tamanho
da mensagem do circuito, o que resulta no seguinte:

O SignalR circuito falha ao inicializar com um erro no cliente: Circuit host not
initialized.
A caixa de diálogo de reconexão no cliente é exibida quando o circuito falha. A
recuperação não é possível.

Para resolver o problema, use uma das seguintes abordagens:

Reduza a quantidade de dados que você está colocando no estado pré-gerado.


Aumente o limite de tamanho daSignalR mensagem. AVISO: aumentar o limite
pode aumentar o risco de ataques de DoS (negação de serviço).

Recursos adicionais Blazor Server


Gerenciamento de estado: manipular a pré-geração
Razor assuntos do ciclo de vida do componente que pertencem à pré-geração
Inicialização de componente (OnInitialized{Async})
Após a renderização do componente (OnAfterRender{Async})
Reconexão com estado após a pré-geração
Pré-geração com interoperabilidade do JavaScript
Autenticação e autorização: aspectos gerais
Tratar erros: Blazor Server pré-geração
Hospedar e implantar: Blazor Server
Mitigação de ameaças: XSS (script entre sites)
Consumir componentes do Razor de
uma Razor biblioteca de classes (RCL)
Artigo • 28/11/2022 • 30 minutos para o fim da leitura

Os componentes podem ser compartilhados em uma Razor RCL (biblioteca de classes)


entre projetos. Inclua componentes e ativos estáticos em um aplicativo de:

Outro projeto na solução.


Uma biblioteca .NET referenciada.
Um pacote NuGet.

Assim como os componentes são tipos .NET regulares, os componentes fornecidos por
uma RCL são assemblies .NET normais.

Criar uma RCL


Visual Studio

1. Criar um novo projeto.


2. Na caixa de diálogo Criar um novo projeto, selecione Razor Biblioteca de
Classes na lista de modelos de projeto ASP.NET Core. Selecione Avançar.
3. Na caixa de diálogo Configurar seu novo projeto , forneça um nome de
projeto no campo Nome do Projeto ou aceite o nome do projeto padrão.
Exemplos neste tópico usam o nome ComponentLibrary do projeto. Selecione
Criar.
4. Na caixa de diálogo Criar uma nova Razor biblioteca de classes , selecione
Criar.
5. Adicione a RCL a uma solução:
a. Abra a solução.
b. Clique com o botão direito do mouse na solução no Gerenciador de
Soluções. Selecione Adicionar>Projeto Existente.
c. Navegue até o arquivo de projeto da RCL.
d. Selecione o arquivo de projeto da RCL ( .csproj ).
6. Adicione uma referência à RCL do aplicativo:
a. Clique com o botão direito do mouse no projeto do aplicativo. Selecione
Adicionar>Referência de Projeto.
b. Selecione o projeto RCL. Selecione OK.
Se a caixa de seleção páginas de suporte e exibições for selecionada para dar
suporte a páginas e exibições ao gerar a RCL do modelo:

Adicione um _Imports.razor arquivo à raiz do projeto RCL gerado com o


seguinte conteúdo para habilitar Razor a criação de componentes:

razor

@using Microsoft.AspNetCore.Components.Web

Adicione o seguinte SupportedPlatform item ao arquivo de projeto ( .csproj ):

XML

<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

Para obter mais informações sobre o SupportedPlatform item, consulte o


analisador de compatibilidade do Navegador para a Blazor WebAssembly
seção.

Consumir um Razor componente de uma RCL


Para consumir componentes de uma RCL em outro projeto, use uma das seguintes
abordagens:

Use o nome completo do tipo de componente, que inclui o namespace da RCL.


Componentes individuais podem ser adicionados pelo nome sem o namespace da
RCL se Razora diretiva 'declarar @using o namespace da RCL. Use as seguintes
abordagens:
Adicione a @using diretiva a componentes individuais.
inclua a @using diretiva no arquivo de nível _Imports.razor superior para
disponibilizar os componentes da biblioteca para um projeto inteiro. Adicione a
diretiva a um _Imports.razor arquivo em qualquer nível para aplicar o
namespace a um único componente ou conjunto de componentes dentro de
uma pasta. Quando um _Imports.razor arquivo é usado, componentes
individuais não exigem uma @using diretiva para o namespace da RCL.

Nos exemplos a seguir, ComponentLibrary há uma RCL que contém o Component1


componente. O Component1 componente é um componente de exemplo adicionado
automaticamente a uma RCL criada a partir do modelo de projeto RCL que não é criado
para dar suporte a páginas e exibições.

7 Observação

Se a RCL for criada para dar suporte a páginas e exibições, adicione manualmente o
Component1 componente e seus ativos estáticos à RCL se você planeja seguir os

exemplos neste artigo. O componente e os ativos estáticos são mostrados nesta


seção.

Component1.razor ComponentLibrary na RCL:

razor

<div class="my-component">
This component is defined in the <strong>ComponentLibrary</strong>
package.
</div>

No aplicativo que consome a RCL, faça referência ao Component1 componente usando


seu namespace, como mostra o exemplo a seguir.

Pages/ConsumeComponent1.razor :

razor

@page "/consume-component-1"

<h1>Consume component (full namespace example)</h1>

<ComponentLibrary.Component1 />

Como alternativa, adicione uma @using diretiva e use o componente sem seu
namespace. A diretiva a seguir @using também pode aparecer em qualquer
_Imports.razor arquivo dentro ou acima da pasta atual.

Pages/ConsumeComponent2.razor :

razor

@page "/consume-component-2"
@using ComponentLibrary

<h1>Consume component (<code>@@using</code> example)</h1>


<Component1 />

Para componentes de biblioteca que usam o isolamento CSS, os estilos de componente


são disponibilizados automaticamente para o aplicativo consumidor. Não é necessário
vincular ou importar manualmente as folhas de estilo de componente individuais da
biblioteca ou seu arquivo CSS empacotado no aplicativo que consome a biblioteca. O
aplicativo usa importações de CSS para fazer referência aos estilos empacotados da RCL.
Os estilos empacotados não são publicados como um ativo da Web estático do
aplicativo que consome a biblioteca. Para uma biblioteca de classes nomeada ClassLib
e um Blazor aplicativo com uma BlazorSample.styles.css folha de estilos, a folha de
estilos da RCL é importada na parte superior da folha de estilos do aplicativo
automaticamente no momento do build:

css

@import '_content/ClassLib/ClassLib.bundle.scp.css';

Para os exemplos anteriores, Component1 a folha de estilos ( Component1.razor.css ) é


agrupada automaticamente.

Component1.razor.css ComponentLibrary na RCL:

css

.my-component {
border: 2px dashed red;
padding: 1em;
margin: 1em 0;
background-image: url('background.png');
}

A imagem em segundo plano também é incluída no modelo de projeto RCL e reside na


wwwroot pasta da RCL.

wwwroot/background.png ComponentLibrary na RCL:

Para fornecer estilos de componente de biblioteca adicionais de folhas de estilos na


pasta da wwwroot biblioteca, adicione marcas de folha <link> de estilos ao consumidor
da RCL, como demonstra o próximo exemplo.
) Importante

Geralmente, os componentes da biblioteca usam o isolamento CSS para agrupar e


fornecer estilos de componente. Os estilos de componente que dependem do
isolamento CSS são disponibilizados automaticamente para o aplicativo que usa a
RCL. Não é necessário vincular ou importar manualmente as folhas de estilos de
componente individuais da biblioteca ou seu arquivo CSS empacotado no
aplicativo que consome a biblioteca. O exemplo a seguir é para fornecer folhas de
estilo globais fora do isolamento do CSS, o que geralmente não é um requisito para
aplicativos típicos que consomem RCLs.

A imagem de plano de fundo a seguir é usada no próximo exemplo. Se você


implementar o exemplo mostrado nesta seção, clique com o botão direito do mouse na
imagem para salvá-la localmente.

wwwroot/extra-background.png ComponentLibrary na RCL:

Adicione uma nova folha de estilos à RCL com uma extra-style classe.

wwwroot/additionalStyles.css ComponentLibrary na RCL:

css

.extra-style {
border: 2px dashed blue;
padding: 1em;
margin: 1em 0;
background-image: url('extra-background.png');
}

Adicione um componente à RCL que usa a extra-style classe.

ExtraStyles.razor ComponentLibrary na RCL:

razor

<div class="extra-style">
<p>
This component is defined in the <strong>ComponentLibrary</strong>
package.
</p>
</div>
Adicione uma página ao aplicativo que usa o ExtraStyles componente da RCL.

Pages/ConsumeComponent3.razor :

razor

@page "/consume-component-3"
@using ComponentLibrary

<h1>Consume component (<code>additionalStyles.css</code> example)</h1>

<ExtraStyles />

Link para a folha de estilos da biblioteca na marcação do <head> aplicativo (local do


<head> conteúdo).

HTML

<link href="_content/ComponentLibrary/additionalStyles.css" rel="stylesheet"


/>

Criar uma RCL com ativos estáticos na wwwroot


pasta
Os ativos estáticos de uma RCL estão disponíveis para qualquer aplicativo que consuma
a biblioteca.

Coloque ativos estáticos na wwwroot pasta da RCL e faça referência aos ativos estáticos
com o seguinte caminho no aplicativo: _content/{PACKAGE ID}/{PATH AND FILE NAME} . O
espaço reservado {PACKAGE ID} é a ID do pacote da biblioteca. A ID do pacote terá
como valor padrão o nome do assembly do projeto, se <PackageId> não for
especificado no arquivo de projeto. O {PATH AND FILE NAME} espaço reservado é o nome
do caminho e do arquivo em wwwroot . Esse formato de caminho também é usado no
aplicativo para ativos estáticos fornecidos por pacotes NuGet adicionados à RCL.

O exemplo a seguir demonstra o uso de ativos estáticos RCL com uma RCL nomeada
ComponentLibrary e um Blazor aplicativo que consome a RCL. O aplicativo tem uma
referência de projeto para a ComponentLibrary RCL.

A imagem do Jeep a® seguir é usada no exemplo desta seção. Se você implementar o


exemplo mostrado nesta seção, clique com o botão direito do mouse na imagem para
salvá-la localmente.
wwwroot/jeep-yj.png ComponentLibrary na RCL:

Adicione o componente a seguir JeepYJ à RCL.

JeepYJ.razor ComponentLibrary na RCL:

razor

<h3>ComponentLibrary.JeepYJ</h3>

<p>
<img alt="Jeep YJ&reg;" src="_content/ComponentLibrary/jeep-yj.png" />
</p>

Adicione o componente a seguir Jeep ao aplicativo que consome a ComponentLibrary


RCL. O Jeep componente usa:

A imagem do Jeep YJ® da ComponentLibrary pasta da wwwroot RCL.


O JeepYJ componente da RCL.

Pages/Jeep.razor :

razor

@page "/jeep"
@using ComponentLibrary

<div style="float:left;margin-right:10px">
<h3>Direct use</h3>

<p>
<img alt="Jeep YJ&reg;" src="_content/ComponentLibrary/jeep-yj.png"
/>
</p>
</div>
<JeepYJ />

<p>
<em>Jeep</em> and <em>Jeep YJ</em> are registered trademarks of
<a href="https://www.stellantis.com">FCA US LLC (Stellantis NV)</a>.
</p>

Componente renderizado Jeep :

Para obter mais informações, consulte A interface do usuário reutilizável Razor em


bibliotecas de classes com ASP.NET Core.

Criar uma RCL com arquivos JavaScript


agrupados com componentes
A colocalização de arquivos JavaScript (JS) para páginas, exibições e componentes Razor
é uma maneira conveniente de organizar scripts em um aplicativo.

Colocalize arquivos JS usando as seguintes convenções de extensão de nome de


arquivo:

Páginas de aplicativos Razor Pages e exibições de aplicativos MVC: .cshtml.js .


Exemplos:
Pages/Index.cshtml.js para a página Index de um aplicativo Razor Pages em
Pages/Index.cshtml .

Views/Home/Index.cshtml.js para a exibição Index de um aplicativo MVC em

Views/Home/Index.cshtml .
Componentes Razor de aplicativos Blazor: .razor.js . Exemplo:
Pages/Index.razor.js para o componente Index em Pages/Index.razor .
Arquivos JS colocalizados são endereçáveis publicamente usando o caminho para o
arquivo no projeto:

Páginas, exibições e componentes de um arquivo de scripts colocalizado no


aplicativo:

{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

O espaço reservado {PATH} é o caminho para a página, exibição ou


componente.
O espaço reservado {PAGE, VIEW, OR COMPONENT} é a página, exibição ou
componente.
O espaço reservado {EXTENSION} corresponde à extensão da página, exibição
ou componente, razor ou cshtml .

Exemplo do Razor Pages:

Um arquivo JS para a página Index é colocado na pasta Pages


( Pages/Index.cshtml.js ) ao lado da página Index ( Pages/Index.cshtml ). Na página
Index , o script é referenciado no caminho na pasta Pages :

razor

@section Scripts {
<script src="~/Pages/Index.cshtml.js"></script>
}

Quando o aplicativo é publicado, a estrutura move automaticamente o script para


a raiz da Web. No exemplo anterior, o script é movido para bin\Release\{TARGET
FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js , em que o espaço

reservado {TARGET FRAMEWORK MONIKER} é o TFM (Moniker da Estrutura de Destino).


Nenhuma alteração é necessária para a URL relativa do script na página Index .

Exemplo de Blazor:

Um arquivo JS para o componente Index é colocado na pasta Pages


( Pages/Index.razor.js ) ao lado do componente Index ( Pages/Index.razor ). No
componente Index , o script é referenciado no caminho na pasta Pages . O
exemplo a seguir é baseado em um exemplo mostrado no artigo Chamar funções
JavaScript de métodos .NET no ASP.NET Core Blazor.

Pages/Index.razor.js :

JavaScript
export function showPrompt(message) {
return prompt(message, 'Type anything here');
}

No método OnAfterRenderAsync do componente Index ( Pages/Index.razor ):

razor

module = await JS.InvokeAsync<IJSObjectReference>(


"import", "./Pages/Index.razor.js");

Quando o aplicativo é publicado, a estrutura move automaticamente o script para


a raiz da Web. No exemplo anterior, o script é movido para bin\Release\{TARGET
FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.razor.js , em que o espaço
reservado {TARGET FRAMEWORK MONIKER} é o TFM (Moniker da Estrutura de Destino).
Nenhuma alteração é necessária para a URL relativa do script no componente
Index .

Para scripts fornecidos por uma RCL (biblioteca de classes) Razor:

_content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js


O espaço reservado {PACKAGE ID} é o identificador do pacote da RCL (ou nome
da biblioteca para uma biblioteca de classes referenciada pelo aplicativo).
O espaço reservado {PATH} é o caminho para a página, exibição ou
componente. Se um componente Razor estiver localizado na raiz da RCL, o
segmento de linha não será incluído.
O espaço reservado {PAGE, VIEW, OR COMPONENT} é a página, exibição ou
componente.
O espaço reservado {EXTENSION} corresponde à extensão da página, exibição
ou componente, razor ou cshtml .

No seguinte exemplo de aplicativo Blazor:


O identificador do pacote da RCL é AppJS .
Os scripts de um módulo são carregados para o componente Index
( Index.razor ).
O componente Index está na pasta Pages da RCL.

C#

var module = await JS.InvokeAsync<IJSObjectReference>("import",


"./_content/AppJS/Pages/Index.razor.js");
Fornecer componentes e ativos estáticos para
vários aplicativos hospedados Blazor
Para obter mais informações, consulte Hospedar e implantar ASP.NET CoreBlazor
WebAssembly.

Analisador de compatibilidade do navegador


para Blazor WebAssembly
Blazor WebAssembly os aplicativos têm como destino a área de superfície completa da
API do .NET, mas nem todas as APIs do .NET têm suporte no WebAssembly devido a
restrições de área restrita do navegador. APIs sem suporte lançadas
PlatformNotSupportedException ao serem executadas no WebAssembly. Um analisador
de compatibilidade de plataforma avisa o desenvolvedor quando o aplicativo usa APIs
que não têm suporte nas plataformas de destino do aplicativo. Para Blazor
WebAssembly aplicativos, isso significa verificar se há suporte para APIs em
navegadores. A anotação de APIs do .NET Framework para o analisador de
compatibilidade é um processo em andamento, portanto, nem todas as API do .NET
Framework estão anotadas no momento.

Blazor WebAssembly Os projetos de RCL e de RCL habilitam automaticamente as


verificações de compatibilidade do navegador adicionando browser como uma
plataforma com suporte com o SupportedPlatform item MSBuild. Os desenvolvedores
de biblioteca podem adicionar manualmente o SupportedPlatform item ao arquivo de
projeto de uma biblioteca para habilitar o recurso:

XML

<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

Ao criar uma biblioteca, indique que não há suporte para


UnsupportedOSPlatformAttributeuma API específica em navegadores
especificando browser :

C#

using System.Runtime.Versioning;

...
[UnsupportedOSPlatform("browser")]
private static string GetLoggingDirectory()
{
...
}

Para obter mais informações, consulte Anotar APIs como sem suporte em plataformas
específicas (repositório GitHub dotnet/designs .

Isolamento de JavaScript em módulos


JavaScript
Blazor habilita o isolamento javaScript em módulos JavaScript padrão. O isolamento
do JavaScript oferece os seguintes benefícios:

O JavaScript importado não polui mais o namespace global.


Os consumidores da biblioteca e dos componentes não são obrigados a importar
manualmente o JavaScript relacionado.

Para obter mais informações, consulte Chamar funções JavaScript de métodos .NET no
ASP.NET Core Blazor.

Compilar, empacotar e enviar para o NuGet


Como Razor as bibliotecas de classes que contêm Razor componentes são bibliotecas
padrão do .NET, empacotar e enviá-las para o NuGet não é diferente de empacotar e
enviar qualquer biblioteca para o NuGet. O empacotamento é executado usando o
dotnet pack comando em um shell de comando:

CLI do .NET

dotnet pack

Carregue o pacote no NuGet usando o dotnet nuget push comando em um shell de


comando.

Marcas Comerciais
Jeep e Jeep YJ são marcas registradas da FCA US LLC (Stellantis NV) .

Recursos adicionais
Interface do usuário do Razor reutilizável em bibliotecas de classes com o ASP.NET
Core
Adicionar um arquivo de configuração il (linguagem intermediária) XML a uma
biblioteca
Suporte ao isolamento CSS com Razor bibliotecas de classes
ASP.NET Core componentes internos
Razor
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Este artigo lista os Razor componentes fornecidos pela Blazor estrutura.

Os seguintes componentes internos Razor são fornecidos pela Blazor estrutura :

App
Authentication
AuthorizeView
CascadingValue
DynamicComponent
ErrorBoundary
FocusOnNavigate
HeadContent
HeadOutlet
InputCheckbox
InputDate
InputFile
InputNumber
InputRadio
InputRadioGroup
InputSelect
InputText
InputTextArea
LayoutView
MainLayout
NavLink
NavMenu
PageTitle
QuickGrid†
Router
RouteView
Virtualize

†O QuickGrid componente está em versão prévia. Você será bem-vindo a usá-lo em


produção se ele atender às suas necessidades, mas ele não tem suporte oficial e pode
mudar em versões futuras.
Blazor globalização e localização
ASP.NET Core
Artigo • 28/11/2022 • 79 minutos para o fim da leitura

Este artigo explica como renderizar conteúdo globalizado e localizado para usuários em
diferentes culturas e idiomas.

Para globalização, Blazor fornece formatação de número e data. Para localização, Blazor
renderiza o conteúdo usando o sistema de recursos do .NET.

Há suporte para um conjunto limitado de recursos de localização do ASP.NET Core:

✔️IStringLocalizer e IStringLocalizer<T> têm suporte em Blazor aplicativos.


❌IHtmlLocalizerA localização de Anotações de Dados , IViewLocalizere são ASP.NET
Core recursos do MVC e não têm suporte em Blazor aplicativos.

Este artigo descreve como usar Blazoros recursos de globalização e localização com
base em:

O Accept-Language cabeçalho , que é definido pelo navegador com base nas


preferências de idioma de um usuário nas configurações do navegador.
Uma cultura definida pelo aplicativo que não se baseia no valor do Accept-
Language cabeçalho . A configuração pode ser estática para todos os usuários ou
dinâmica com base na lógica do aplicativo. Quando a configuração é baseada na
preferência do usuário, a configuração geralmente é salva para recarregar em
visitas futuras.

Para obter informações gerais adicionais, consulte os seguintes recursos:

Globalização e localização no ASP.NET Core


Conceitos básicos do .NET: globalização
Conceitos básicos do .NET: localização

7 Observação

Muitas vezes, os termos linguagem e cultura são usados de forma intercambiável


ao lidar com conceitos de globalização e localização.

Neste artigo, o idioma refere-se a seleções feitas por um usuário nas configurações
do navegador. As seleções de idioma do usuário são enviadas em solicitações de
navegador no Accept-Language cabeçalho . As configurações do navegador
geralmente usam a palavra "idioma" na interface do usuário.

A cultura pertence a membros do .NET e Blazor da API. Por exemplo, a solicitação


de um usuário pode incluir o Accept-Language cabeçalho especificando um
idioma da perspectiva do usuário, mas o aplicativo, em última análise, define a
CurrentCulture propriedade ("cultura") do idioma solicitado pelo usuário. A API
geralmente usa a palavra "cultura" em seus nomes de membro.

Globalização
A @bind diretiva de atributo aplica formatos e analisa valores para exibição com base
no primeiro idioma preferencial do usuário ao qual o aplicativo dá suporte. @bind dá
suporte ao @bind:culture parâmetro para fornecer um System.Globalization.CultureInfo
para analisar e formatar um valor.

A cultura atual pode ser acessada da System.Globalization.CultureInfo.CurrentCulture


propriedade .

CultureInfo.InvariantCulture é usado para os seguintes tipos de campo ( <input type="


{TYPE}" /> , em que o {TYPE} espaço reservado é o tipo):

date

number

Os tipos de campo anteriores:

São exibidos usando as regras de formatação apropriadas baseadas em


navegador.
Não pode conter texto de forma livre.
Forneça características de interação do usuário com base na implementação do
navegador.

Ao usar os date tipos de campo e number , especificar uma cultura com @bind:culture
não é recomendado porque Blazor fornece suporte interno para renderizar valores na
cultura atual.

Os seguintes tipos de campo têm requisitos de formatação específicos e não têm


suporte no momento porque Blazor não têm suporte de todos os principais
navegadores:

datetime-local
month

week

Para obter suporte ao navegador atual dos tipos anteriores, consulte Posso usar .

Globalização invariável
Se o aplicativo não exigir localização, configure o aplicativo para dar suporte à cultura
invariável, que geralmente se baseia em Estados Unidos inglês ( en-US ). Defina a
InvariantGlobalization propriedade como true no arquivo de projeto do aplicativo

( .csproj ):

XML

<PropertyGroup>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

Como alternativa, configure a globalização invariável com as seguintes abordagens:

Em runtimeconfig.json :

JSON

{
"runtimeOptions": {
"configProperties": {
"System.Globalization.Invariant": true
}
}
}

Com uma variável de ambiente:


Chave: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
Valor: true ou 1

Para obter mais informações, consulte Opções de configuração de runtime para


globalização (documentação do .NET).

Componente de demonstração
CultureExample1 O componente a seguir pode ser usado para demonstrar Blazor os

conceitos de globalização e localização abordados por este artigo.


Pages/CultureExample1.razor :

razor

@page "/culture-example-1"
@using System.Globalization

<h1>Culture Example 1</h1>

<p>
<b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Rendered values</h2>

<ul>
<li><b>Date</b>: @dt</li>
<li><b>Number</b>: @number.ToString("N2")</li>
</ul>

<h2><code>&lt;input&gt;</code> elements that don't set a <code>type</code>


</h2>

<p>
The following <code>&lt;input&gt;</code> elements use
<code>CultureInfo.CurrentCulture</code>.
</p>

<ul>
<li><label><b>Date:</b> <input @bind="dt" /></label></li>
<li><label><b>Number:</b> <input @bind="number" /></label></li>
</ul>

<h2><code>&lt;input&gt;</code> elements that set a <code>type</code></h2>

<p>
The following <code>&lt;input&gt;</code> elements use
<code>CultureInfo.InvariantCulture</code>.
</p>

<ul>
<li><label><b>Date:</b> <input type="date" @bind="dt" /></label></li>
<li><label><b>Number:</b> <input type="number" @bind="number" /></label>
</li>
</ul>

@code {
private DateTime dt = DateTime.Now;
private double number = 1999.69;
}
O formato de cadeia de caracteres numérica ( N2 ) no exemplo anterior
( .ToString("N2") ) é um especificador de formato numérico padrão do .NET. O N2
formato tem suporte para todos os tipos numéricos, inclui um separador de grupo e
renderiza até duas casas decimais.

Opcionalmente, adicione um item de menu à navegação no Shared/NavMenu.razor para


o CultureExample1 componente.

Definir dinamicamente a cultura do Accept-


Language cabeçalho
O Accept-Language cabeçalho é definido pelo navegador e controlado pelas
preferências de idioma do usuário nas configurações do navegador. Nas configurações
do navegador, um usuário define um ou mais idiomas preferenciais em ordem de
preferência. A ordem de preferência é usada pelo navegador para definir valores de
qualidade ( q , 0-1) para cada idioma no cabeçalho. O exemplo a seguir especifica
Estados Unidos inglês, inglês e espanhol chileno com preferência por Estados Unidos
inglês ou inglês:

Accept-Language: en-US,en;q=0.9,es-CL;q=0.8

A cultura do aplicativo é definida pela correspondência do primeiro idioma solicitado


que corresponde a uma cultura com suporte do aplicativo.

Blazor Server os aplicativos são localizados usando o Middleware de Localização.


Adicione serviços de localização ao aplicativo com AddLocalization.

Em Program.cs :

C#

builder.Services.AddLocalization();

Especifique as culturas com suporte do aplicativo imediatamente Program.cs após o


Middleware de Roteamento ser adicionado ao pipeline de processamento. O exemplo a
seguir configura culturas com suporte para Estados Unidos inglês e espanhol chileno:

C#

app.UseRequestLocalization(new RequestLocalizationOptions()
.AddSupportedCultures(new[] { "en-US", "es-CL" })
.AddSupportedUICultures(new[] { "en-US", "es-CL" }));
Para obter informações sobre como ordenar o Middleware de Localização no pipeline
de middleware do Program.cs , consulte ASP.NET Core Middleware.

Use o CultureExample1 componente mostrado na seção Componente demonstração


para estudar como a globalização funciona. Emita uma solicitação com Estados Unidos
inglês ( en-US ). Alterne para espanhol chileno ( es-CL ) nas configurações de idioma do
navegador. Solicite a página da Web novamente.

7 Observação

Alguns navegadores forçam você a usar a configuração de idioma padrão para


solicitações e as próprias configurações de interface do usuário do navegador. Isso
pode dificultar a alteração do idioma para um que você entenda difícil porque
todas as telas de configuração da interface do usuário podem acabar em um
idioma que você não pode ler. Um navegador como o Opera é uma boa opção
para teste porque permite que você defina um idioma padrão para solicitações de
página da Web, mas deixe a interface do usuário de configurações do navegador
em seu idioma.

Quando a cultura é Estados Unidos inglês ( en-US ), o componente renderizado usa


formatação de data de mês/dia ( 6/7 ), hora de 12 horas ( PM AM /) e separadores de
vírgulas em números com um ponto para o valor decimal (): 1,999.69

Data: 7/06/2021 6:45:22 AM


Número: 1.999,69

Quando a cultura é espanhol chileno ( es-CL ), o componente renderizado usa


formatação de data de dia/mês ( 7/6 ), tempo de 24 horas e separadores de período em
números com uma vírgula para o valor decimal ( 1.999,69 ):

Data: 6/07/2021 6:49:38


Número: 1.999,69

Definir estaticamente a cultura


Blazor Server os aplicativos são localizados usando o Middleware de Localização.
Adicione serviços de localização ao aplicativo com AddLocalization.

Em Program.cs :

C#
builder.Services.AddLocalization();

Especifique a cultura estática imediatamente Program.cs após o middleware de


roteamento ser adicionado ao pipeline de processamento. O exemplo a seguir configura
Estados Unidos inglês:

C#

app.UseRequestLocalization("en-US");

O valor de cultura para UseRequestLocalization deve estar em conformidade com o


formato de marca de idioma BCP-47 .

Para obter informações sobre como ordenar o Middleware de Localização no pipeline


de middleware do Program.cs , consulte ASP.NET Core Middleware.

Use o CultureExample1 componente mostrado na seção Componente demonstração


para estudar como a globalização funciona. Emita uma solicitação com Estados Unidos
inglês ( en-US ). Alterne para espanhol chileno ( es-CL ) nas configurações de idioma do
navegador. Solicite a página da Web novamente. Quando o idioma solicitado é
espanhol chileno, a cultura do aplicativo permanece Estados Unidos inglês ( en-US ).

Definir dinamicamente a cultura por


preferência do usuário
Exemplos de locais em que um aplicativo pode armazenar a preferência de um usuário
incluem no armazenamento local do navegador (comum em Blazor WebAssembly
aplicativos), em uma localização cookie ou banco de dados (comum em Blazor Server
aplicativos) ou em um serviço externo anexado a um banco de dados externo e
acessado por uma API Web. O exemplo a seguir demonstra como usar uma localização
cookie.

Adicione o Microsoft.Extensions.Localization pacote ao aplicativo.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .
Blazor Server os aplicativos são localizados usando o Middleware de Localização.
Adicione serviços de localização ao aplicativo com AddLocalization.

Em Program.cs :

C#

builder.Services.AddLocalization();

Defina as culturas padrão e com suporte do aplicativo com RequestLocalizationOptions.

No Program.cs imediatamente após o middleware de roteamento ser adicionado ao


pipeline de processamento:

C#

var supportedCultures = new[] { "en-US", "es-CL" };


var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

Para obter informações sobre como ordenar o Middleware de Localização no pipeline


de middleware do Program.cs , consulte ASP.NET Core Middleware.

O exemplo a seguir mostra como definir a cultura atual em um cookie que pode ser lido
pelo Middleware de Localização.

As modificações no Pages/_Host.cshtml arquivo exigem os seguintes namespaces:

System.Globalization
Microsoft.AspNetCore.Localization

Pages/_Host.cshtml :

diff

+ @using System.Globalization
+ @using Microsoft.AspNetCore.Localization
+ @{
+ this.HttpContext.Response.Cookies.Append(
+ CookieRequestCultureProvider.DefaultCookieName,
+ CookieRequestCultureProvider.MakeCookieValue(
+ new RequestCulture(
+ CultureInfo.CurrentCulture,
+ CultureInfo.CurrentUICulture)));
+ }

Para obter informações sobre como ordenar o Middleware de Localização no pipeline


de middleware do Program.cs , consulte ASP.NET Core Middleware.

Se o aplicativo não estiver configurado para processar ações do controlador:

Adicione serviços MVC chamando AddControllers na coleção de serviços em


Program.cs :

C#

builder.Services.AddControllers();

Adicione o roteamento de ponto de extremidade do controlador ao Program.cs


chamar MapControllers no IEndpointRouteBuilder:

C#

app.MapControllers();

O exemplo a seguir mostra a chamada para UseEndpoints depois que a linha é


adicionada:

C#

app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

Para fornecer interface do usuário para permitir que um usuário selecione uma cultura,
use uma abordagem baseada em redirecionamento com uma localização cookie. O
aplicativo persiste a cultura selecionada do usuário por meio de um redirecionamento
para um controlador. O controlador define a cultura selecionada do usuário em um
cookie e redireciona o usuário de volta para o URI original. O processo é semelhante ao
que acontece em um aplicativo Web quando um usuário tenta acessar um recurso
seguro, em que o usuário é redirecionado para uma página de entrada e, em seguida,
redirecionado de volta para o recurso original.

Controllers/CultureController.cs :

C#
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;

[Route("[controller]/[action]")]
public class CultureController : Controller
{
public IActionResult Set(string culture, string redirectUri)
{
if (culture != null)
{
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture, culture)));
}

return LocalRedirect(redirectUri);
}
}

2 Aviso

Use o resultado da LocalRedirect ação para evitar ataques de redirecionamento


abertos. Para obter mais informações, consulte Impedir ataques de
redirecionamento aberto em ASP.NET Core.

CultureSelector O componente a seguir mostra como chamar o Set método do

CultureController com a nova cultura. O componente é colocado na Shared pasta para


uso em todo o aplicativo.

Shared/CultureSelector.razor :

razor

@using System.Globalization
@inject NavigationManager Navigation

<p>
<label>
Select your locale:
<select @bind="Culture">
@foreach (var culture in supportedCultures)
{
<option value="@culture">@culture.DisplayName</option>
}
</select>
</label>
</p>
@code
{
private CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("es-CL"),
};

protected override void OnInitialized()


{
Culture = CultureInfo.CurrentCulture;
}

private CultureInfo Culture


{
get => CultureInfo.CurrentCulture;
set
{
if (CultureInfo.CurrentCulture != value)
{
var uri = new Uri(Navigation.Uri)
.GetComponents(UriComponents.PathAndQuery,
UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(value.Name);
var uriEscaped = Uri.EscapeDataString(uri);

Navigation.NavigateTo(
$"Culture/Set?culture={cultureEscaped}&redirectUri=
{uriEscaped}",
forceLoad: true);
}
}
}
}

Dentro da marca de fechamento </main> em Shared/MainLayout.razor , adicione o


CultureSelector componente :

razor

<article class="bottom-row px-4">


<CultureSelector />
</article>

Use o CultureExample1 componente mostrado na seção Componente demonstração


para estudar como o exemplo anterior funciona.

Localização
Se o aplicativo ainda não der suporte à seleção de cultura de acordo com a seção
Defina dinamicamente a cultura por preferência do usuário deste artigo, adicione o
Microsoft.Extensions.Localization pacote ao aplicativo.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Use o Middleware de Localização para definir a cultura do aplicativo.

Se o aplicativo ainda não der suporte à seleção de cultura de acordo com a seção
Defina dinamicamente a cultura por preferência do usuário deste artigo:

Adicione serviços de localização ao aplicativo com AddLocalization.


Especifique as culturas padrão e com suporte do aplicativo em Program.cs . O
exemplo a seguir configura culturas com suporte para Estados Unidos inglês e
espanhol chileno.

Em Program.cs :

C#

builder.Services.AddLocalization();

No Program.cs imediatamente após o middleware de roteamento ser adicionado ao


pipeline de processamento:

C#

var supportedCultures = new[] { "en-US", "es-CL" };


var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

Para obter informações sobre como ordenar o Middleware de Localização no pipeline


de middleware do Program.cs , consulte ASP.NET Core Middleware.
Se o aplicativo deve localizar recursos com base no armazenamento da configuração de
cultura de um usuário, use uma cultura cookiede localização . O uso de um cookie
garante que a conexão WebSocket possa propagar corretamente a cultura. Se os
esquemas de localização forem baseados no caminho da URL ou na cadeia de
caracteres de consulta, o esquema poderá não ser capaz de trabalhar com WebSockets,
portanto, falha ao persistir a cultura. Portanto, a abordagem recomendada é usar uma
cultura cookiede localização . Consulte a seção Definir dinamicamente a cultura por
preferência do usuário deste artigo para ver uma expressão de exemplo Razor que
persiste a seleção de cultura do usuário.

O exemplo de recursos localizados nesta seção funciona com os exemplos anteriores


neste artigo em que as culturas com suporte do aplicativo são inglês ( en ) como
localidade padrão e espanhol ( es ) como uma localidade alternativa selecionada pelo
usuário ou especificada pelo navegador.

Crie recursos para cada localidade. No exemplo a seguir, os recursos são criados para
uma cadeia de caracteres padrão Greeting :

Inglês: Hello, World!


Espanhol ( es ): ¡Hola, Mundo!

7 Observação

O arquivo de recurso a seguir pode ser adicionado no Visual Studio clicando com o
botão direito do mouse na pasta do Pages projeto e selecionando
Adicionar>Novo ArquivodeRecursos de Item>. Atribua um nome ao arquivo
CultureExample2.resx . Quando o editor for exibido, forneça dados para uma nova

entrada. Defina o Nome como Greeting e Valor como Hello, World! . Salve o
arquivo.

Pages/CultureExample2.resx :

XML

<?xml version="1.0" encoding="utf-8"?>


<root>
<xsd:schema id="root" xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0"
msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"
msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string"
msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string"
msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Greeting" xml:space="preserve">
<value>Hello, World!</value>
</data>
</root>

7 Observação

O arquivo de recurso a seguir pode ser adicionado no Visual Studio clicando com o
botão direito do mouse na pasta do Pages projeto e selecionando
Adicionar>Novo ArquivodeRecursos de Item>. Atribua um nome ao arquivo
CultureExample2.es.resx . Quando o editor for exibido, forneça dados para uma

nova entrada. Defina o Nome como Greeting e Valor como ¡Hola, Mundo! . Salve o
arquivo.

Pages/CultureExample2.es.resx :

XML

<?xml version="1.0" encoding="utf-8"?>


<root>
<xsd:schema id="root" xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0"
msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"
msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string"
msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string"
msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Greeting" xml:space="preserve">
<value>¡Hola, Mundo!</value>
</data>
</root>

O componente a seguir demonstra o uso da cadeia de caracteres localizada Greeting


com IStringLocalizer<T>. A Razor marcação @Loc["Greeting"] no exemplo a seguir
localiza a cadeia de caracteres chaveada para o Greeting valor , que é definido nos
arquivos de recurso anteriores.

Adicione o namespace para Microsoft.Extensions.Localization ao arquivo do


_Imports.razor aplicativo:

razor

@using Microsoft.Extensions.Localization

Pages/CultureExample2.razor :

razor

@page "/culture-example-2"
@using System.Globalization
@inject IStringLocalizer<CultureExample2> Loc

<h1>Culture Example 2</h1>

<p>
<b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Greeting</h2>

<p>
@Loc["Greeting"]
</p>

<p>
@greeting
</p>

@code {
private string? greeting;

protected override void OnInitialized()


{
greeting = Loc["Greeting"];
}
}

Opcionalmente, adicione um item de menu à navegação no Shared/NavMenu.razor para


o CultureExample2 componente.

Recursos compartilhados
Para criar recursos compartilhados de localização, adote a abordagem a seguir.

Crie uma classe fictícia com um nome de classe arbitrário. No exemplo a seguir:
O aplicativo usa o namespace e os BlazorSample ativos de localização usam o
BlazorSample.Localization namespace.
A classe fictícia é chamada SharedResource .
O arquivo de classe é colocado em uma Localization pasta na raiz do
aplicativo.

Localization/SharedResource.cs :

C#

namespace BlazorSample.Localization
{
public class SharedResource
{
}
}

Crie os arquivos de recursos compartilhados com uma Ação de Build de Embedded


resource . No exemplo a seguir:

Os arquivos são colocados na Localization pasta com a classe fictícia


SharedResource ( Localization/SharedResource.cs ).

Nomeie os arquivos de recurso para corresponder ao nome da classe fictícia. Os


arquivos de exemplo a seguir incluem um arquivo de localização padrão e um
arquivo para localização em espanhol ( es ).

Localization/SharedResource.resx

Localization/SharedResource.es.resx

7 Observação

Localization é o caminho do recurso que pode ser definido por meio de


LocalizationOptions.

Para fazer referência à classe fictícia de um injetado IStringLocalizer<T> em um


Razor componente, coloque uma @using diretiva para o namespace de localização
ou inclua o namespace de localização na referência de classe fictícia. Nos seguintes
exemplos:
O primeiro exemplo indica o Localization namespace da SharedResource
classe fictícia com uma @using diretiva .
O segundo exemplo indica explicitamente o SharedResource namespace da
classe fictícia.

Em um Razor componente, use uma das seguintes abordagens:

razor

@using Localization
@inject IStringLocalizer<SharedResource> Loc

razor

@inject IStringLocalizer<Localization.SharedResource> Loc

Para obter diretrizes adicionais, consulte Globalização e localização no ASP.NET Core.

Recursos adicionais
Definir o caminho base do aplicativo
Globalização e localização no ASP.NET Core
Globalizando e localizando aplicativos do .NET
Recursos em arquivos .resx
Kit de Ferramentas de Aplicativo Multilíngue da Microsoft
Genéricos de localização &
Chamar InvokeAsync(StateHasChanged) faz com que a página faça fallback para a
cultura padrão (dotnet/aspnetcore #28521)
Blazor ASP.NET Core formulários e
componentes de entrada
Artigo • 10/01/2023 • 143 minutos para o fim da leitura

A Blazor estrutura dá suporte a formulários e fornece componentes de entrada internos:

EditForm componente associado a um modelo que usa anotações de dados


Componentes de entrada internos

O Microsoft.AspNetCore.Components.Forms namespace fornece:

Classes para gerenciar elementos de formulário, estado e validação.


Acesso a componentes internos Input* , que podem ser usados em Blazor
aplicativos.

Um projeto criado com base no Blazor modelo de projeto inclui o namespace por
padrão no arquivo do _Imports.razor aplicativo, o que disponibiliza o namespace em
todos os arquivos de Razor componente ( .razor ) do aplicativo sem diretivas explícitas
@using :

razor

@using Microsoft.AspNetCore.Components.Forms

Para demonstrar como um EditForm componente funciona, considere o exemplo a


seguir. ExampleModel representa o modelo de dados associado ao formulário e define
uma Name propriedade , que é usada para armazenar o valor do campo do name
formulário fornecido pelo usuário.

ExampleModel.cs :

C#

public class ExampleModel


{
public string? Name { get; set; }
}

Um formulário é definido usando o Blazor componente da EditForm estrutura. Razor O


componente a seguir demonstra elementos, componentes e Razor código típicos para
renderizar uma forma da Web usando um EditForm componente, que está associado ao
tipo anterior ExampleModel .
Pages/FormExample1.razor :

razor

@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger

<EditForm Model="@exampleModel" OnSubmit="@HandleSubmit">


<InputText id="name" @bind-Value="exampleModel.Name" />

<button type="submit">Submit</button>
</EditForm>

@code {
private ExampleModel exampleModel = new();

private void HandleSubmit()


{
Logger.LogInformation("HandleSubmit called");

// Process the form


}
}

No componente anterior FormExample1 :

O EditForm componente é renderizado onde o <EditForm> elemento é exibido.


O modelo é criado no bloco do @code componente e mantido em um campo
privado ( exampleModel ). O campo é atribuído ao EditForm.Modelatributo ( Model )
do <EditForm> elemento .
O InputText componente ( id="name" ) é um componente de entrada para editar
valores de cadeia de caracteres. O @bind-Value atributo de diretiva associa a
propriedade do exampleModel.Name modelo à InputText propriedade do Value
componente.
O HandleSubmit método é registrado como um manipulador para o OnSubmit
retorno de chamada. O manipulador é chamado quando o formulário é enviado
pelo usuário.

Para demonstrar como o componente anterior EditForm funciona com a validação de


anotações de dados :

O anterior ExampleModel usa o System.ComponentModel.DataAnnotations


namespace .
A Name propriedade de é marcada como obrigatória com o RequiredAttribute e
especifica um StringLengthAttribute limite máximo de comprimento de cadeia de
caracteres e uma mensagem de ExampleModel erro.

ExampleModel.cs :

C#

using System.ComponentModel.DataAnnotations;

public class ExampleModel


{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}

O componente anterior FormExample1 é modificado:

OnSubmit é substituído OnValidSubmitpor , que processa o manipulador de


eventos atribuído se o formulário for válido quando enviado pelo usuário. O nome
do método é alterado para HandleValidSubmit , o que reflete que o método é
chamado quando o formulário é válido.
Um ValidationSummary componente é adicionado para exibir mensagens de
validação quando o formulário é inválido no envio do formulário.
O validador de anotações de dados (DataAnnotationsValidator componente†)
anexa o suporte à validação usando anotações de dados:
Se o <input> campo de formulário for deixado em branco quando o Submit
botão for selecionado, um erro será exibido no resumo de validação
(ValidationSummary component‡) (" The Name field is required. ") e
HandleValidSubmit não será chamado.

Se o <input> campo de formulário contiver mais de dez caracteres quando o


Submit botão for selecionado, um erro aparecerá no resumo de validação

(" Name is too long. ") e HandleValidSubmit não será chamado.


Se o <input> campo de formulário contiver um valor válido quando o Submit
botão for selecionado, HandleValidSubmit será chamado.

†O DataAnnotationsValidator componente é abordado na seção Componente validador .


‡O ValidationSummary componente é abordado na seção Resumo de validação e
componentes da mensagem de validação . Para obter mais informações sobre a
associação de propriedade, consulte ASP.NET Core Blazor associação de dados.

Pages/FormExample1.razor :

razor
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">


<DataAnnotationsValidator />
<ValidationSummary />

<InputText id="name" @bind-Value="exampleModel.Name" />

<button type="submit">Submit</button>
</EditForm>

@code {
private ExampleModel exampleModel = new();

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called");

// Process the valid form


}
}

Associando um formulário
Um EditForm cria um EditContext com base na instância de modelo atribuída como um
valor em cascata para outros componentes no formulário. O EditContext rastreia
metadados sobre o processo de edição, incluindo quais campos foram modificados e as
mensagens de validação atuais. Atribuir a um EditForm.Model ou um
EditForm.EditContext pode associar um formulário a dados.

Atribuição a EditForm.Model:

razor

<EditForm Model="@exampleModel" ...>

@code {
private ExampleModel exampleModel = new() { ... };
}

Atribuição a EditForm.EditContext:

razor

<EditForm EditContext="@editContext" ...>


@code {
private ExampleModel exampleModel = new() { ... };
private EditContext? editContext;

protected override void OnInitialized()


{
editContext = new(exampleModel);
}
}

Atribua um EditContextou um Model a um EditForm. A atribuição de ambos não tem


suporte e gera um erro de runtime:

Componente de renderização de exceção sem tratamento: EditForm requer um


parâmetro Model ou um parâmetro EditContext, mas não ambos.

Manipular envio de formulário


O EditForm fornece os seguintes retornos de chamada para lidar com o envio de
formulário:

Use OnValidSubmit para atribuir um manipulador de eventos a ser executado


quando um formulário com campos válidos for enviado.
Use OnInvalidSubmit para atribuir um manipulador de eventos a ser executado
quando um formulário com campos inválidos for enviado.
Use OnSubmit para atribuir um manipulador de eventos a ser executado
independentemente do status de validação dos campos de formulário. O
formulário é validado chamando EditContext.Validate no método do manipulador
de eventos. Se Validate retornar true , o formulário será válido.

Componentes de entrada internos


A Blazor estrutura fornece componentes de entrada internos para receber e validar a
entrada do usuário. Os componentes de entrada internos na tabela a seguir têm suporte
em um EditForm com um EditContext e fora de um formulário na Razor marcação de
componente. As entradas são validadas quando são alteradas e quando um formulário é
enviado.

Componente de entrada Renderizado como...

InputCheckbox <input type="checkbox">

InputDate<TValue> <input type="date">


Componente de entrada Renderizado como...

InputFile <input type="file">

InputNumber<TValue> <input type="number">

InputRadio<TValue> <input type="radio">

InputRadioGroup<TValue> Grupo de filhos InputRadio<TValue>

InputSelect<TValue> <select>

InputText <input>

InputTextArea <textarea>

Todos os componentes de entrada, incluindo EditForm, dão suporte a atributos


arbitrários. Qualquer atributo que não corresponda a um parâmetro de componente é
adicionado ao elemento HTML renderizado.

Os componentes de entrada fornecem o comportamento padrão para validação quando


um campo é alterado:

Para componentes de entrada em um formulário com um EditContext, o


comportamento de validação padrão inclui atualizar a classe CSS do campo para
refletir o estado do campo como válido ou inválido com o estilo de validação do
elemento HTML subjacente.
Para controles que não têm um EditContext, a validação padrão reflete o estado
válido ou inválido, mas não fornece estilo de validação para o elemento HTML
subjacente.

Alguns componentes incluem lógica de análise útil. Por exemplo, InputDate<TValue> e


InputNumber<TValue> manipulam valores nãoparsáveis normalmente registrando
valores nãoparáveis como erros de validação. Os tipos que podem aceitar valores nulos
também dão suporte à nulidade do campo de destino (por exemplo, int? para um
inteiro anulável).

O InputFile componente é um pouco mais complexo e é detalhado em ASP.NET Core


Blazor uploads de arquivo.

Formulário de exemplo
O seguinte Starship tipo, que é usado em vários dos exemplos deste artigo, define um
conjunto diversificado de propriedades com anotações de dados:
Identifier é necessário porque é anotado com o RequiredAttribute. Identifier

requer um valor de pelo menos um caractere, mas não mais do que 16 caracteres
usando o StringLengthAttribute.
Description é opcional porque não é anotado com o RequiredAttribute.
Classification é obrigatório.

A MaximumAccommodation propriedade usa como padrão zero, mas requer um valor


de um a 100.000 por seu RangeAttribute.
IsValidatedDesign requer que a propriedade tenha um true valor , que

corresponde a um estado selecionado quando a propriedade está associada a uma


caixa de seleção na interface do usuário ( <input type="checkbox"> ).
ProductionDate é um DateTime e obrigatório.

Starship.cs :

C#

using System.ComponentModel.DataAnnotations;

public class Starship


{
[Required]
[StringLength(16, ErrorMessage = "Identifier too long (16 character
limit).")]
public string? Identifier { get; set; }

public string? Description { get; set; }

[Required]
public string? Classification { get; set; }

[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]


public int MaximumAccommodation { get; set; }

[Required]
[Range(typeof(bool), "true", "true",
ErrorMessage = "This form disallows unapproved ships.")]
public bool IsValidatedDesign { get; set; }

[Required]
public DateTime ProductionDate { get; set; }
}

O seguinte formulário aceita e valida a entrada do usuário usando:

As propriedades e a validação definidas no modelo anterior Starship .


Vários dos Blazorcomponentes de entrada internos do .
Pages/FormExample2.razor :

razor

@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">


<DataAnnotationsValidator />
<ValidationSummary />

<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" />
</label>
</p>

<button type="submit">Submit</button>

<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>

@code {
private Starship starship = new() { ProductionDate = DateTime.UtcNow };

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called");

// Process the valid form


}
}

O EditForm no exemplo anterior cria um EditContext com base na instância atribuída


Starship ( Model="@starship" ) e manipula um formulário válido. O próximo exemplo

( FormExample3 componente) demonstra como atribuir um EditContext a um formulário e


validar quando o formulário é enviado.

No exemplo a seguir:

Uma versão abreviada do formulário anterior Starfleet Starship Database


( FormExample2 componente) é usada que aceita apenas um valor para o
identificador da nave estelar. As outras Starship propriedades recebem valores
padrão válidos quando uma instância do Starship tipo é criada.
O HandleSubmit método é executado quando o Submit botão é selecionado.
O formulário é validado chamando EditContext.Validate no HandleSubmit método .
O registro em log é executado dependendo do resultado da validação.

7 Observação

HandleSubmit no FormExample3 componente é demonstrado como um método

assíncrono porque armazenar valores de formulário geralmente usa chamadas


assíncronas ( await ... ). Se o formulário for usado em um aplicativo de teste,
conforme mostrado, HandleSubmit apenas será executado de forma síncrona. Para
fins de teste, ignore o seguinte aviso de build:
O método assíncrono não possui operadores 'await' e será executado de forma
síncrona. ...

Pages/FormExample3.razor :

razor

@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger

<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">


<DataAnnotationsValidator />
<ValidationSummary />

<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>

<button type="submit">Submit</button>

<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>

@code {
private Starship starship =
new()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private EditContext? editContext;

protected override void OnInitialized()


{
editContext = new(starship);
}

private async Task HandleSubmit()


{
if (editContext != null && editContext.Validate())
{
Logger.LogInformation("HandleSubmit called: Form is valid");

// Process the valid form


// await ...
await Task.CompletedTask;
}
else
{
Logger.LogInformation("HandleSubmit called: Form is INVALID");
}
}
}

7 Observação

Não há suporte para alterar o EditContext após sua atribuição.

Seleção de várias opções com o InputSelect


componente
A associação dá multiple suporte à seleção de opção com o InputSelect<TValue>
componente . O @onchange evento fornece uma matriz das opções selecionadas por
meio de argumentos de evento (ChangeEventArgs). O valor deve ser associado a um
tipo de matriz e a associação a um tipo de matriz torna o multiple atributo opcional
na InputSelect<TValue> marca.

No exemplo a seguir, o usuário deve selecionar pelo menos duas classificações de


starship, mas não mais do que três classificações.

Pages/BindMultipleWithInputSelect.razor :

razor

@page "/bind-multiple-with-inputselect"
@using System.ComponentModel.DataAnnotations
@using Microsoft.Extensions.Logging
@inject ILogger<BindMultipleWithInputSelect> Logger

<h1>Bind Multiple <code>InputSelect</code>Example</h1>

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">


<DataAnnotationsValidator />
<ValidationSummary />

<p>
<label>
Select classifications (Minimum: 2, Maximum: 3):
<InputSelect @bind-Value="starship.SelectedClassification">
<option
value="@Classification.Exploration">Exploration</option>
<option value="@Classification.Diplomacy">Diplomacy</option>
<option value="@Classification.Defense">Defense</option>
<option value="@Classification.Research">Research</option>
</InputSelect>
</label>
</p>

<button type="submit">Submit</button>
</EditForm>

<p>
Selected Classifications:
@string.Join(", ", starship.SelectedClassification)
</p>

@code {
private EditContext? editContext;
private Starship starship = new();

protected override void OnInitialized()


{
editContext = new(starship);
}

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called");
}

private class Starship


{
[Required, MinLength(2), MaxLength(3)]
public Classification[] SelectedClassification { get; set; } =
new[] { Classification.Diplomacy };
}

private enum Classification { Exploration, Diplomacy, Defense, Research


}
}

Para obter informações sobre como cadeias de caracteres e null valores vazios são
tratados na associação de dados, consulte a seção Opções InputSelect de associação a
valores de objeto null C #.
Opções de associação InputSelect a valores de
objeto null C#
Para obter informações sobre como cadeias de caracteres e null valores vazios são
tratados na associação de dados, consulte ASP.NET Core Blazor associação de dados.

Suporte a nome de exibição


Vários componentes internos dão suporte a nomes de exibição com o
InputBase<TValue>.DisplayName parâmetro .

Starfleet Starship Database No formulário ( FormExample2 componente) da seção


Formulário de exemplo, a data de produção de uma nova nave estelar não especifica
um nome de exibição:

razor

<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" />
</label>

Se o campo contiver uma data inválida quando o formulário for enviado, a mensagem
de erro não exibirá um nome amigável. O nome do campo , " ProductionDate " não tem
um espaço entre " Production " e " Date " quando ele aparece no resumo de validação:

O campo ProductionDate deve ser uma data.

Defina a DisplayName propriedade como um nome amigável com um espaço entre as


palavras " Production " e " Date ":

razor

<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate"
DisplayName="Production Date" />
</label>

O resumo da validação exibe o nome amigável quando o valor do campo é inválido:

O campo Data de Produção deve ser uma data.


Suporte ao modelo de mensagem de erro
InputDate<TValue> e InputNumber<TValue> dão suporte a modelos de mensagem de
erro:

InputDate<TValue>.ParsingErrorMessage
InputNumber<TValue>.ParsingErrorMessage

Starfleet Starship Database No formulário ( FormExample2 componente) da seção


Formulário de exemplo com um nome de exibição amigável atribuído, o Production
Date campo produz uma mensagem de erro usando o seguinte modelo de mensagem

de erro padrão:

css

The {0} field must be a date.

A posição do {0} espaço reservado é onde o valor da DisplayName propriedade


aparece quando o erro é exibido para o usuário.

razor

<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate"
DisplayName="Production Date" />
</label>

O campo Data de Produção deve ser uma data.

Atribua um modelo personalizado a ParsingErrorMessage para fornecer uma mensagem


personalizada:

razor

<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate"
DisplayName="Production Date"
ParsingErrorMessage="The {0} field has an incorrect date
value." />
</label>

O campo Data de Produção tem um valor de data incorreto.


Validação básica
Em cenários de validação de formulário básico, uma EditForm instância pode usar
instâncias declaradas EditContext e ValidationMessageStore para validar campos de
formulário. Um manipulador para o OnValidationRequested evento do executa a
EditContext lógica de validação personalizada. O resultado do manipulador atualiza a
ValidationMessageStore instância.

A validação de formulário básico é útil nos casos em que o modelo do formulário é


definido dentro do componente que hospeda o formulário, seja como membros
diretamente no componente ou em uma subclasse. O uso de um componente validador
é recomendado quando uma classe de modelo independente é usada em vários
componentes.

No componente a seguir FormExample4 , o HandleValidationRequested método de


manipulador limpa todas as mensagens de validação existentes chamando
ValidationMessageStore.Clear antes de validar o formulário.

Pages/FormExample4.razor :

razor

@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger

<h2>Ship Holodecks</h2>

<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">


<label>
Type 1:
<InputCheckbox @bind-Value="holodeck.Type1" />
</label>

<label>
Type 2:
<InputCheckbox @bind-Value="holodeck.Type2" />
</label>

<button type="submit">Update</button>

<ValidationMessage For="() => holodeck.Options" />

<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>

@code {
private EditContext? editContext;
private Holodeck holodeck = new();
private ValidationMessageStore? messageStore;

protected override void OnInitialized()


{
editContext = new(holodeck);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}

private void HandleValidationRequested(object? sender,


ValidationRequestedEventArgs args)
{
messageStore?.Clear();

// Custom validation logic


if (!holodeck.Options)
{
messageStore?.Add(() => holodeck.Options, "Select at least
one.");
}
}

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called: Processing the
form");

// Process the form


}

public class Holodeck


{
public bool Type1 { get; set; }
public bool Type2 { get; set; }
public bool Options => Type1 || Type2;
}

public void Dispose()


{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
Componente validador de anotações de dados
e validação personalizada
O DataAnnotationsValidator componente anexa a validação de anotações de dados a
um em cascata EditContext. Habilitar a validação de anotações de dados requer o
DataAnnotationsValidator componente . Para usar um sistema de validação diferente
das anotações de dados, use uma implementação personalizada em vez do
DataAnnotationsValidator componente . As implementações de estrutura para
DataAnnotationsValidator estão disponíveis para inspeção na fonte de referência:

DataAnnotationsValidator
AddDataAnnotationsValidation .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Blazor executa dois tipos de validação:

A validação de campo é executada quando o usuário sai de um campo. Durante a


validação de campo, o DataAnnotationsValidator componente associa todos os
resultados de validação relatados ao campo .
A validação do modelo é executada quando o usuário envia o formulário. Durante
a validação do modelo, o DataAnnotationsValidator componente tenta determinar
o campo com base no nome do membro que o resultado da validação relata. Os
resultados de validação que não estão associados a um membro individual são
associados ao modelo em vez de a um campo.

Componentes do validador
Os componentes do validador dão suporte à validação de formulário gerenciando um
ValidationMessageStore para o de EditContextum formulário.

A Blazor estrutura fornece o DataAnnotationsValidator componente para anexar suporte


de validação a formulários com base em atributos de validação (anotações de dados).
Você pode criar componentes de validador personalizados para processar mensagens
de validação para formulários diferentes na mesma página ou no mesmo formulário em
diferentes etapas de processamento de formulário (por exemplo, validação do lado do
cliente seguida pela validação do lado do servidor). O exemplo de componente
validador mostrado nesta seção, CustomValidation , é usado nas seções a seguir deste
artigo:

Validação de lógica de negócios com um componente validador


Validação de servidor com um componente validador

7 Observação

Atributos de validação de anotação de dados personalizados podem ser usados em


vez de componentes de validador personalizados em muitos casos. Atributos
personalizados aplicados ao modelo do formulário são ativados com o uso do
DataAnnotationsValidator componente. Quando usado com validação do lado do
servidor, todos os atributos personalizados aplicados ao modelo devem ser
executáveis no servidor. Para obter mais informações, consulte Validação de
modelo no ASP.NET Core MVC.

Crie um componente validador de ComponentBase:

O do EditContext formulário é um parâmetro em cascata do componente.


Quando o componente validador é inicializado, um novo ValidationMessageStore
é criado para manter uma lista atual de erros de formulário.
O repositório de mensagens recebe erros quando o código do desenvolvedor no
componente do formulário chama o DisplayErrors método . Os erros são
passados para o DisplayErrors método em um Dictionary<string, List<string>>.
No dicionário, a chave é o nome do campo de formulário que tem um ou mais
erros. O valor é a lista de erros.
As mensagens são limpas quando qualquer uma das seguintes opções ocorre:
A validação é solicitada no EditContext quando o OnValidationRequested
evento é gerado. Todos os erros são limpos.
Um campo é alterado no formulário quando o OnFieldChanged evento é
gerado. Somente os erros do campo são limpos.
O ClearErrors método é chamado pelo código do desenvolvedor. Todos os
erros são limpos.

CustomValidation.cs (se usado em um aplicativo de teste, altere o namespace ,

BlazorSample para corresponder ao namespace do aplicativo):

C#
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample
{
public class CustomValidation : ComponentBase
{
private ValidationMessageStore? messageStore;

[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }

protected override void OnInitialized()


{
if (CurrentEditContext is null)
{
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} "
+
$"inside an {nameof(EditForm)}.");
}

messageStore = new(CurrentEditContext);

CurrentEditContext.OnValidationRequested += (s, e) =>


messageStore?.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore?.Clear(e.FieldIdentifier);
}

public void DisplayErrors(Dictionary<string, List<string>> errors)


{
if (CurrentEditContext is not null)
{
foreach (var err in errors)
{
messageStore?.Add(CurrentEditContext.Field(err.Key),
err.Value);
}

CurrentEditContext.NotifyValidationStateChanged();
}
}

public void ClearErrors()


{
messageStore?.Clear();
CurrentEditContext?.NotifyValidationStateChanged();
}
}
}
) Importante

Especificar um namespace é necessário ao derivar de ComponentBase. Falha ao


especificar um namespace resulta em um erro de build:

Os auxiliares de marca não podem direcionar o nome da marca '<namespace>


global.{ CLASS NAME}' porque contém um caractere ' ' .

O {CLASS NAME} espaço reservado é o nome da classe de componente. O exemplo


de validador personalizado nesta seção especifica o namespace BlazorSample de
exemplo .

7 Observação

Expressões lambda anônimas são manipuladores de eventos registrados para


OnValidationRequested e OnFieldChanged no exemplo anterior. Não é necessário
implementar IDisposable e cancelar a assinatura dos delegados de eventos nesse
cenário. Para saber mais, consulte Ciclo de vida de renderização de Razor no
ASP.NET Core.

Validação de lógica de negócios com um


componente validador
Para validação geral da lógica de negócios, use um componente validador que receba
erros de formulário em um dicionário.

A validação básica é útil nos casos em que o modelo do formulário é definido dentro do
componente que hospeda o formulário, seja como membros diretamente no
componente ou em uma subclasse. O uso de um componente validador é recomendado
quando uma classe de modelo independente é usada em vários componentes.

No exemplo a seguir:

Uma versão abreviada do Starfleet Starship Database formulário ( FormExample2


componente) da seção Formulário de exemplo é usada que aceita apenas a
classificação e a descrição da nave estelar. A validação de anotação de dados não
é disparada no envio de formulário porque o DataAnnotationsValidator
componente não está incluído no formulário.
O CustomValidation componente da seção Componentes do validador deste
artigo é usado.
A validação exigirá um valor para a descrição da nave ( Description ) se o usuário
selecionar a classificação de remessa " Defense " ( Classification ).

Quando as mensagens de validação são definidas no componente, elas são adicionadas


ao validador ValidationMessageStore e mostradas no EditFormresumo de validação do .

Pages/FormExample5.razor :

razor

@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">


<CustomValidation @ref="customValidation" />
<ValidationSummary />

<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>

<button type="submit">Submit</button>

<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>

@code {
private CustomValidation? customValidation;
private Starship starship = new() { ProductionDate = DateTime.UtcNow };

private void HandleValidSubmit()


{
customValidation?.ClearErrors();

var errors = new Dictionary<string, List<string>>();

if (starship.Classification == "Defense" &&


string.IsNullOrEmpty(starship.Description))
{
errors.Add(nameof(starship.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}

if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("HandleValidSubmit called: Processing the
form");

// Process the valid form


}
}
}

7 Observação

Como alternativa ao uso de componentes de validação, os atributos de validação


de anotação de dados podem ser usados. Atributos personalizados aplicados ao
modelo do formulário são ativados com o uso do DataAnnotationsValidator
componente. Quando usados com a validação do lado do servidor, os atributos
devem ser executáveis no servidor. Para obter mais informações, consulte
Validação de modelo no ASP.NET Core MVC.

Validação de servidor com um componente


validador
Há suporte para validação de servidor além da validação do lado do cliente:

Processe a validação do lado do cliente no formulário com o


DataAnnotationsValidator componente .
Quando o formulário passar pela validação do lado do cliente (OnValidSubmit é
chamado), envie o EditContext.Model para uma API de servidor back-end para
processamento de formulários.
Validação do modelo de processo no servidor.
A API do servidor inclui a validação de anotações de dados da estrutura interna e a
lógica de validação personalizada fornecida pelo desenvolvedor. Se a validação for
aprovada no servidor, processe o formulário e envie de volta um código de status
de êxito (200 - OK ). Se a validação falhar, retorne um código de status de falha
(400 - Bad Request ) e os erros de validação de campo.
Desabilite o formulário com êxito ou exiba os erros.

A validação básica é útil nos casos em que o modelo do formulário é definido dentro do
componente que hospeda o formulário, seja como membros diretamente no
componente ou em uma subclasse. O uso de um componente validador é recomendado
quando uma classe de modelo independente é usada em vários componentes.

O exemplo a seguir baseia-se em:

Uma solução hospedada Blazor WebAssembly criada com base no Blazor


WebAssembly modelo de projeto. A abordagem tem suporte para qualquer uma
das soluções hospedadas Blazor seguras descritas na documentação de segurança
hospedadaBlazor WebAssembly.
O Starship modelo ( Starship.cs ) da seção Formulário de exemplo .
O CustomValidation componente mostrado na seção Componentes do Validador .

Coloque o Starship modelo ( Starship.cs ) no projeto da Shared solução para que os


aplicativos cliente e servidor possam usar o modelo. Adicione ou atualize o namespace
para corresponder ao namespace do aplicativo compartilhado (por exemplo, namespace
BlazorSample.Shared ). Como o modelo requer anotações de dados, adicione o

System.ComponentModel.Annotations pacote ao Shared projeto.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Server No projeto, adicione um controlador para processar solicitações de validação de


starship e retornar mensagens de validação com falha. Atualize os namespaces na
última using instrução para o Shared projeto e o namespace para a classe de
controlador. Além da validação de anotações de dados (lado do cliente e do lado do
servidor), o controlador valida que um valor é fornecido para a descrição da nave
( Description ) se o usuário selecionar a classificação de Defense remessa
( Classification ).

A validação para a classificação de Defense remessa só ocorre no lado do servidor no


controlador porque o formulário futuro não executa o mesmo lado do cliente de
validação quando o formulário é enviado ao servidor. A validação do lado do servidor
sem validação do lado do cliente é comum em aplicativos que exigem validação de
lógica de negócios privada da entrada do usuário no servidor. Por exemplo, informações
privadas de dados armazenados para um usuário podem ser necessárias para validar a
entrada do usuário. Os dados privados obviamente não podem ser enviados ao cliente
para validação do lado do cliente.

7 Observação

O StarshipValidation controlador nesta seção usa o Microsoft Identity 2.0. A API


Web aceita apenas tokens para usuários que têm o escopo " API.Access " dessa API.
A personalização adicional será necessária se o nome do escopo da API for
diferente de API.Access . Para obter uma versão do controlador que funciona com
o Microsoft Identity 1.0 e ASP.NET Core antes da versão 5.0, consulte uma versão
anterior deste artigo.

Para obter mais informações sobre segurança, consulte:

Blazor ASP.NET Core autenticação e autorização (e os outros artigos no


Blazor nó e Identitysegurança)
Documentação da plataforma de identidade da Microsoft

Controllers/StarshipValidation.cs :

C#

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Web.Resource;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController : ControllerBase
{
private readonly ILogger<StarshipValidationController> logger;

public StarshipValidationController(
ILogger<StarshipValidationController> logger)
{
this.logger = logger;
}

static readonly string[] scopeRequiredByApi = new[] { "API.Access"


};

[HttpPost]
public async Task<IActionResult> Post(Starship starship)
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

try
{
if (starship.Classification == "Defense" &&
string.IsNullOrEmpty(starship.Description))
{
ModelState.AddModelError(nameof(starship.Description),
"For a 'Defense' ship " +
"classification, 'Description' is required.");
}
else
{
logger.LogInformation("Processing the form
asynchronously");

// Process the valid form


// async ...

return Ok(ModelState);
}
}
catch (Exception ex)
{
logger.LogError("Validation Error: {Message}", ex.Message);
}

return BadRequest(ModelState);
}
}
}

Se estiver usando o controlador anterior em um aplicativo hospedado Blazor


WebAssembly , atualize o namespace ( BlazorSample.Server.Controllers ) para
corresponder ao namespace de controladores do aplicativo.
Quando ocorre um erro de validação de associação de modelo no servidor, um
ApiController (ApiControllerAttribute) normalmente retorna uma resposta de solicitação
inválida padrão com um ValidationProblemDetails. A resposta contém mais dados do
que apenas os erros de validação, conforme mostrado no exemplo a seguir, quando
todos os campos do Starfleet Starship Database formulário não são enviados e o
formulário falha na validação:

JSON

{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Identifier": ["The Identifier field is required."],
"Classification": ["The Classification field is required."],
"IsValidatedDesign": ["This form disallows unapproved ships."],
"MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}
}

7 Observação

Para demonstrar a resposta ON anterior JS, você deve desabilitar a validação do


lado do cliente do formulário para permitir o envio vazio do formulário de campo
ou usar uma ferramenta para enviar uma solicitação diretamente à API do servidor,
como o Desenvolvedor do Firefox Browser ou o Postman .

Se a API do servidor retornar a resposta ON padrão JSanterior, será possível que o


cliente analise a resposta no código do desenvolvedor para obter os filhos do errors
nó para processamento de erro de validação de formulários. É inconveniente escrever
código do desenvolvedor para analisar o arquivo. Analisar o JSON manualmente requer
a produção de um Dictionary<string, List<string>> dos erros após chamar
ReadFromJsonAsync. O ideal é que a API do servidor retorne apenas os erros de
validação:

JSON

{
"Identifier": ["The Identifier field is required."],
"Classification": ["The Classification field is required."],
"IsValidatedDesign": ["This form disallows unapproved ships."],
"MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}
Para modificar a resposta da API do servidor para que ela retorne apenas os erros de
validação, altere o delegado invocado em ações que são anotadas com
ApiControllerAttribute no Program.cs . Para o ponto de extremidade da API
( /StarshipValidation ), retorne um BadRequestObjectResult com o
ModelStateDictionary. Para quaisquer outros pontos de extremidade de API, preserve o
comportamento padrão retornando o resultado do objeto com um novo
ValidationProblemDetails.

Adicione o Microsoft.AspNetCore.Mvc namespace à parte superior do Program.cs


arquivo no Server aplicativo:

C#

using Microsoft.AspNetCore.Mvc;

No Program.cs , localize o AddControllersWithViews método de extensão e adicione a


seguinte chamada a ConfigureApiBehaviorOptions:

C#

builder.Services.AddControllersWithViews()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (context.HttpContext.Request.Path == "/StarshipValidation")
{
return new BadRequestObjectResult(context.ModelState);
}
else
{
return new BadRequestObjectResult(
new ValidationProblemDetails(context.ModelState));
}
};
});

Para obter mais informações, consulte Manipular erros em APIs Web ASP.NET Core.

Client No projeto, adicione o CustomValidation componente mostrado na seção


Componentes do Validador. Atualize o namespace para corresponder ao aplicativo (por
exemplo, namespace BlazorSample.Client ).

Client No projeto, o Starfleet Starship Database formulário é atualizado para mostrar


erros de validação do servidor com a ajuda do CustomValidation componente. Quando
a API do servidor retorna mensagens de validação, elas são adicionadas ao
CustomValidation do ValidationMessageStorecomponente . Os erros estão disponíveis

no formulário EditContext para exibição pelo resumo de validação do formulário.

No componente a seguir FormExample6 , atualize o namespace do Shared projeto


( @using BlazorSample.Shared ) para o namespace do projeto compartilhado. Observe
que o formulário requer autorização, portanto, o usuário deve ser conectado ao
aplicativo para navegar até o formulário.

Pages/FormExample6.razor :

razor

@page "/form-example-6"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Logging
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<FormExample6> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">


<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />

<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification"
disabled="@disabled">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate"
disabled="@disabled" />
</label>
</p>

<button type="submit" disabled="@disabled">Submit</button>

<p style="@messageStyles">
@message
</p>

<p>
<a href="http://www.startrek.com/">Star Trek</a>,
&copy;1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>

@code {
private bool disabled;
private string? message;
private string? messageStyles = "visibility:hidden";
private CustomValidation? customValidation;
private Starship starship = new() { ProductionDate = DateTime.UtcNow };

private async Task HandleValidSubmit(EditContext editContext)


{
customValidation?.ClearErrors();

try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);

var errors = await response.Content


.ReadFromJsonAsync<Dictionary<string, List<string>>>();

if (response.StatusCode == HttpStatusCode.BadRequest &&


errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Validation failed. Status Code:
{response.StatusCode}");
}
else
{
disabled = true;
messageStyles = "color:green";
message = "The form has been processed.";
}
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
catch (Exception ex)
{
Logger.LogError("Form processing error: {Message}", ex.Message);
disabled = true;
messageStyles = "color:red";
message = "There was an error processing the form.";
}
}
}

7 Observação

Como alternativa ao uso de um componente de validação, os atributos de


validação de anotação de dados podem ser usados. Atributos personalizados
aplicados ao modelo do formulário são ativados com o uso do
DataAnnotationsValidator componente. Quando usados com validação do lado do
servidor, os atributos devem ser executáveis no servidor. Para obter mais
informações, consulte Validação de modelo no ASP.NET Core MVC.

7 Observação
A abordagem de validação do lado do servidor nesta seção é adequada para
qualquer um dos exemplos de solução hospedada Blazor WebAssembly neste
conjunto de documentação:

AAD (Azure Active Directory)


Azure Active Directory (AAD) B2C
Servidor Identity

InputText com base no evento de entrada


Use o InputText componente para criar um componente personalizado que usa o
oninput evento (input ) em vez do onchange evento (change ). Uso da validação do
input campo de gatilhos de evento em cada pressionamento de tecla.

O exemplo a seguir usa a ExampleModel classe .

ExampleModel.cs :

C#

using System.ComponentModel.DataAnnotations;

public class ExampleModel


{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}

O componente a seguir CustomInputText herda o componente da InputText estrutura e


define a associação de eventos para o oninput evento (input ).

Shared/CustomInputText.razor :

razor

@inherits InputText

<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
O CustomInputText componente pode ser usado em qualquer lugar InputText . O
componente a seguir FormExample7 usa o componente compartilhado CustomInputText .

Pages/FormExample7.razor :

razor

@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">


<DataAnnotationsValidator />
<ValidationSummary />

<CustomInputText @bind-Value="exampleModel.Name" />

<button type="submit">Submit</button>
</EditForm>

<p>
CurrentValue: @exampleModel.Name
</p>

@code {
private ExampleModel exampleModel = new();

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called");

// Process the valid form


}
}

Botões de opção
O exemplo nesta seção baseia-se na Starfleet Starship Database forma da seção
Formulário de exemplo deste artigo.

Adicione os seguintesenum tipos ao aplicativo. Crie um novo arquivo para mantê-los ou


adicioná-los ao Starship.cs arquivo.

C#

public class ComponentEnums


{
public enum Manufacturer { SpaceX, NASA, ULA, VirginGalactic, Unknown }
public enum Color { ImperialRed, SpacecruiserGreen, StarshipBlue,
VoyagerOrange }
public enum Engine { Ion, Plasma, Fusion, Warp }
}

Torne o enums acessível para:

Starship no Starship.cs modelo (por exemplo, using static ComponentEnums; se


a enums classe for chamada ComponentEnums ).
Starfleet Starship Database form (por exemplo, @using static ComponentEnums
se a classe enumeração for chamada ComponentEnums ).

Use InputRadio<TValue> componentes com o InputRadioGroup<TValue> componente


para criar um grupo de botões de opção. No exemplo a seguir, as propriedades são
adicionadas ao Starship modelo descrito na seção Formulário de exemplo :

C#

[Required]
[Range(typeof(Manufacturer), nameof(Manufacturer.SpaceX),
nameof(Manufacturer.VirginGalactic), ErrorMessage = "Pick a
manufacturer.")]
public Manufacturer Manufacturer { get; set; } = Manufacturer.Unknown;

[Required, EnumDataType(typeof(Color))]
public Color? Color { get; set; } = null;

[Required, EnumDataType(typeof(Engine))]
public Engine? Engine { get; set; } = null;

Atualize o Starfleet Starship Database formulário ( FormExample2 componente) da


seção Formulário de exemplo . Adicione os componentes a serem produzidos:

Um grupo de botões de opção para o fabricante da nave.


Um grupo de botões de opção aninhados para a cor do mecanismo e da nave.

7 Observação

Os grupos de botões de opção aninhados não são frequentemente usados em


formulários porque podem resultar em um layout desorganizado de controles de
formulário que podem confundir os usuários. No entanto, há casos em que fazem
sentido no design da interface do usuário, como no exemplo a seguir, que
emparelha recomendações para duas entradas de usuário, o mecanismo de
remessa e a cor da nave. Um mecanismo e uma cor são exigidos pela validação do
formulário. O layout do formulário usa aninhados InputRadioGroup<TValue>para
emparelhar recomendações de mecanismo e cor. No entanto, o usuário pode
combinar qualquer mecanismo com qualquer cor para enviar o formulário.

razor

<p>
<InputRadioGroup @bind-Value="starship.Manufacturer">
Manufacturer:
<br>
@foreach (var manufacturer in (Manufacturer[])Enum
.GetValues(typeof(Manufacturer)))
{
<InputRadio Value="@manufacturer" />
<text>&nbsp;</text>@manufacturer<br>
}
</InputRadioGroup>
</p>
<p>
Select one engine and one color. Recommendations are paired but any
combination of engine and color is allowed:<br>
<InputRadioGroup Name="engine" @bind-Value="starship.Engine">
<InputRadioGroup Name="color" @bind-Value="starship.Color">
<InputRadio Name="engine" Value="@Engine.Ion" />
Engine: Ion<br>
<InputRadio Name="color" Value="@Color.ImperialRed" />
Color: Imperial Red<br><br>
<InputRadio Name="engine" Value="@Engine.Plasma" />
Engine: Plasma<br>
<InputRadio Name="color" Value="@Color.SpacecruiserGreen" />
Color: Spacecruiser Green<br><br>
<InputRadio Name="engine" Value="@Engine.Fusion" />
Engine: Fusion<br>
<InputRadio Name="color" Value="@Color.StarshipBlue" />
Color: Starship Blue<br><br>
<InputRadio Name="engine" Value="@Engine.Warp" />
Engine: Warp<br>
<InputRadio Name="color" Value="@Color.VoyagerOrange" />
Color: Voyager Orange
</InputRadioGroup>
</InputRadioGroup>
</p>

7 Observação

Se Name for omitido, InputRadio<TValue> os componentes serão agrupados por


seu ancestral mais recente.
Componentes de Mensagem de Validação e
Resumo de Validação
O ValidationSummary componente resume todas as mensagens de validação, que são
semelhantes ao Auxiliar de Marca de Resumo de Validação:

razor

<ValidationSummary />

Mensagens de validação de saída para um modelo específico com o Model parâmetro :

razor

<ValidationSummary Model="@starship" />

O ValidationMessage<TValue> componente exibe mensagens de validação para um


campo específico, que é semelhante ao Auxiliar de Marca de Mensagem de Validação.
Especifique o campo para validação com o For atributo e uma expressão lambda
nomeando a propriedade do modelo:

razor

<ValidationMessage For="@(() => starship.MaximumAccommodation)" />

Os ValidationMessage<TValue> componentes e ValidationSummary dão suporte a


atributos arbitrários. Qualquer atributo que não corresponda a um parâmetro de
componente é adicionado ao elemento gerado <div> ou <ul> .

Controlar o estilo das mensagens de validação na folha de estilos do aplicativo


( wwwroot/css/app.css ou wwwroot/css/site.css ). A classe padrão validation-message
define a cor do texto das mensagens de validação como vermelho:

css

.validation-message {
color: red;
}

Atributos de validação personalizados


Para garantir que um resultado de validação esteja corretamente associado a um campo
ao usar um atributo de validação personalizado, passe o contexto de validação ao
MemberName criar o ValidationResult.

CustomValidator.cs :

C#

using System;
using System.ComponentModel.DataAnnotations;

public class CustomValidator : ValidationAttribute


{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
...

return new ValidationResult("Validation message to user.",


new[] { validationContext.MemberName });
}
}

Injete serviços em atributos de validação personalizados por meio do ValidationContext.


O exemplo a seguir demonstra um formulário salad chef que valida a entrada do
usuário com DI (injeção de dependência).

A SaladChef classe indica a lista de ingredientes de frutas aprovados para uma salada.

SaladChef.cs :

C#

public class SaladChef


{
public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple",
"Honeydew", "Watermelon", "Grapes" };
}

Registre-se SaladChef no contêiner de DI do aplicativo em Program.cs :

C#

builder.Services.AddTransient<SaladChef>();

O IsValid método da classe a seguir SaladChefValidatorAttribute obtém o SaladChef


serviço de DI para verificar a entrada do usuário.
SaladChefValidatorAttribute.cs :

C#

using System.ComponentModel.DataAnnotations;

public class SaladChefValidatorAttribute : ValidationAttribute


{
protected override ValidationResult? IsValid(object? value,
ValidationContext validationContext)
{
var saladChef = validationContext.GetRequiredService<SaladChef>();

if (saladChef.ThingsYouCanPutInASalad.Contains(value?.ToString()))
{
return ValidationResult.Success;
}

return new ValidationResult("You should not put that in a salad!");


}
}

ValidationWithDI O componente a seguir valida a entrada do usuário aplicando o

SaladChefValidatorAttribute ( [SaladChefValidator] ) à cadeia de caracteres de

ingrediente de salada ( SaladIngredient ).

Pages/ValidationWithDI.razor :

razor

@page "/validation-with-di"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms

<EditForm Model="@this" autocomplete="off">


<DataAnnotationsValidator />

<p>
Name something you can put in a salad:
<input @bind="SaladIngredient" />
</p>

<button type="submit">Submit</button>

<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>

@code {
[SaladChefValidator]
public string? SaladIngredient { get; set; }
}

Atributos de classe CSS de validação


personalizada
Atributos de classe CSS de validação personalizada são úteis ao integrar-se a estruturas
CSS, como Bootstrap .

O exemplo a seguir usa a ExampleModel classe .

ExampleModel.cs :

C#

using System.ComponentModel.DataAnnotations;

public class ExampleModel


{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}

Para especificar atributos de classe CSS de validação personalizada, comece fornecendo


estilos CSS para validação personalizada. No exemplo a seguir, estilos válidos
( validField ) e inválidos ( invalidField ) são especificados.

wwwroot/css/app.css (Blazor WebAssembly) ou wwwroot/css/site.css (Blazor Server):

css

.validField {
border-color: lawngreen;
}

.invalidField {
background-color: tomato;
}

Crie uma classe derivada de FieldCssClassProvider que verifica mensagens de validação


de campo e aplica o estilo válido ou inválido apropriado.
CustomFieldClassProvider.cs :

C#

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider


{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid =
!editContext.GetValidationMessages(fieldIdentifier).Any();

return isValid ? "validField" : "invalidField";


}
}

Defina a CustomFieldClassProvider classe como o Provedor de Classe CSS de Campo na


instância do EditContext formulário com SetFieldCssClassProvider.

Pages/FormExample8.razor :

razor

@page "/form-example-8"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample8> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">


<DataAnnotationsValidator />
<ValidationSummary />

<InputText id="name" @bind-Value="exampleModel.Name" />

<button type="submit">Submit</button>
</EditForm>

@code {
private ExampleModel exampleModel = new();
private EditContext? editContext;

protected override void OnInitialized()


{
editContext = new(exampleModel);
editContext.SetFieldCssClassProvider(new
CustomFieldClassProvider());
}

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}

O exemplo anterior verifica a validade de todos os campos de formulário e aplica um


estilo a cada campo. Se o formulário só deve aplicar estilos personalizados a um
subconjunto dos campos, torne CustomFieldClassProvider os estilos de aplicação
condicionalmente. O exemplo a seguir CustomFieldClassProvider2 aplica apenas um
estilo ao Name campo . Para todos os campos com nomes que não correspondem Name a
, string.Empty é retornado e nenhum estilo é aplicado.

CustomFieldClassProvider2.cs :

C#

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider


{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid =
!editContext.GetValidationMessages(fieldIdentifier).Any();

return isValid ? "validField" : "invalidField";


}

return string.Empty;
}
}

Adicione uma propriedade adicional a ExampleModel , por exemplo:

C#

[StringLength(10, ErrorMessage = "Description is too long.")]


public string? Description { get; set; }

Adicione o Description ao ExampleForm7 formulário do componente:

razor

<InputText id="description" @bind-Value="exampleModel.Description" />


Atualize a EditContext instância no método do OnInitialized componente para usar o
novo Provedor de Classe CSS de Campo:

C#

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

Como uma classe de validação CSS não é aplicada ao Description campo


( id="description" ), ela não é estilizada. No entanto, a validação de campo é executada
normalmente. Se mais de 10 caracteres forem fornecidos, o resumo da validação
indicará o erro:

A descrição é muito longa.

No exemplo a seguir:

O estilo CSS personalizado é aplicado ao Name campo .

Quaisquer outros campos aplicam lógica semelhante à Blazorlógica padrão de e


usando Blazoros estilos de validação CSS do campo padrão, modified com valid
ou invalid . Observe que, para os estilos padrão, você não precisará adicioná-los à
folha de estilos do aplicativo se o aplicativo for baseado em um Blazor modelo de
projeto. Para aplicativos não baseados em um Blazor modelo de projeto, os estilos
padrão podem ser adicionados à folha de estilos do aplicativo:

css

.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}

.invalid {
outline: 1px solid red;
}

CustomFieldClassProvider3.cs :

C#

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider


{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid =
!editContext.GetValidationMessages(fieldIdentifier).Any();

if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}

Atualize a EditContext instância no método do OnInitialized componente para usar o


provedor de classe CSS de campo anterior:

C#

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

Usando CustomFieldClassProvider3 :

O Name campo usa os estilos CSS de validação personalizada do aplicativo.


O Description campo usa lógica semelhante aos Blazorestilos de validação CSS do
campo padrão e da lógica Blazor.

Blazor pacote de validação de anotações de


dados
O Microsoft.AspNetCore.Components.DataAnnotations.Validation é um pacote que
preenche lacunas de experiência de validação usando o DataAnnotationsValidator
componente . No momento, o pacote é experimental.

2 Aviso
O Microsoft.AspNetCore.Components.DataAnnotations.Validation pacote tem
uma versão mais recente do candidato à versão em NuGet.org . Continue a usar o
pacote de candidatos à versão experimental neste momento. Os recursos
experimentais são fornecidos com a finalidade de explorar a viabilidade do recurso
e podem não ser fornecidos em uma versão estável. Assista ao repositório GitHub
comunicados , ao repositório gitHub dotnet/aspnetcore ou a seção deste
tópico para obter mais atualizações.

Modelos aninhados, tipos de coleção e tipos


complexos
Blazor fornece suporte para validar a entrada de formulário usando anotações de dados
com o interno DataAnnotationsValidator. No entanto, o DataAnnotationsValidator só
valida as propriedades de nível superior do modelo associadas ao formulário que não
são propriedades de coleção ou de tipo complexo.

Para validar todo o grafo de objeto do modelo associado, incluindo propriedades de


coleção e tipo complexo, use o ObjectGraphDataAnnotationsValidator fornecido pelo
pacote experimentalMicrosoft.AspNetCore.Components.DataAnnotations.Validation :

razor

<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">


<ObjectGraphDataAnnotationsValidator />
...
</EditForm>

Anotar propriedades do modelo com [ValidateComplexType] . Nas seguintes classes de


modelo, a ShipDescription classe contém anotações de dados adicionais para validar
quando o modelo está associado ao formulário:

Starship.cs :

C#

using System;
using System.ComponentModel.DataAnnotations;

public class Starship


{
...

[ValidateComplexType]
public ShipDescription ShipDescription { get; set; } = new();
...
}

ShipDescription.cs :

C#

using System;
using System.ComponentModel.DataAnnotations;

public class ShipDescription


{
[Required]
[StringLength(40, ErrorMessage = "Description too long (40 char).")]
public string? ShortDescription { get; set; }

[Required]
[StringLength(240, ErrorMessage = "Description too long (240 char).")]
public string? LongDescription { get; set; }
}

Habilitar o botão enviar com base na validação


do formulário
Para habilitar e desabilitar o botão enviar com base na validação do formulário, o
exemplo a seguir:

Usa uma versão abreviada do formulário anterior Starfleet Starship Database


( FormExample2 componente) que aceita apenas um valor para o identificador da
nave. As outras Starship propriedades recebem valores padrão válidos quando
uma instância do Starship tipo é criada.
Usa o do EditContext formulário para atribuir o modelo quando o componente é
inicializado.
Valida o formulário no retorno de chamada do OnFieldChanged contexto para
habilitar e desabilitar o botão enviar.
IDisposable Implementa e cancela a assinatura do manipulador de eventos no
Dispose método . Para saber mais, consulte Ciclo de vida de renderização de Razor
no ASP.NET Core.

7 Observação
Ao atribuir ao EditForm.EditContext, também não atribua um EditForm.Model ao
EditForm.

Pages/FormExample9.razor :

razor

@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">


<DataAnnotationsValidator />
<ValidationSummary />

<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>

<button type="submit" disabled="@formInvalid">Submit</button>


</EditForm>

@code {
private Starship starship =
new()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private bool formInvalid = false;
private EditContext? editContext;

protected override void OnInitialized()


{
editContext = new(starship);
editContext.OnFieldChanged += HandleFieldChanged;
}

private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)


{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called");

// Process the valid form


}

public void Dispose()


{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}

Se um formulário não for pré-carregado com valores válidos e você quiser desabilitar o
Submit botão no carregamento do formulário, defina formInvalid como true .

Um efeito colateral da abordagem anterior é que um resumo de validação


(ValidationSummary componente) é preenchido com campos inválidos depois que o
usuário interage com qualquer campo. Resolva esse cenário de qualquer uma das
seguintes maneiras:

Não use um ValidationSummary componente no formulário.


Torne o ValidationSummary componente visível quando o botão enviar estiver
selecionado (por exemplo, em um HandleValidSubmit método).

razor

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">


<DataAnnotationsValidator />
<ValidationSummary style="@displaySummary" />

...

<button type="submit" disabled="@formInvalid">Submit</button>


</EditForm>

@code {
private string displaySummary = "display:none";

...

private void HandleValidSubmit()


{
displaySummary = "display:block";
}
}
Solucionar problemas
InvalidOperationException: EditForm requer um parâmetro Model ou um parâmetro
EditContext, mas não ambos.

Confirme se o EditForm atribui um Modelou um EditContext. Não use ambos para o


mesmo formulário.

Ao atribuir a Model, confirme se o tipo de modelo é instanciado, como mostra o


exemplo a seguir:

C#

private ExampleModel exampleModel = new();

Recursos adicionais
Blazor carregamentos de arquivo ASP.NET Core
Proteger um aplicativo de ASP.NET Core Blazor WebAssembly hospedado com o
Azure Active Directory
Proteger um aplicativo de ASP.NET Core Blazor WebAssembly hospedado com o
Azure Active Directory B2C
Proteger um aplicativo de ASP.NET Core Blazor WebAssembly hospedado com
Identity o Servidor
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Blazor uploads de arquivo ASP.NET Core
Artigo • 06/01/2023 • 69 minutos para o fim da leitura

Este artigo explica como carregar arquivos com Blazor o InputFile componente .

2 Aviso

Sempre siga as práticas recomendadas de segurança ao permitir que os usuários


carreguem arquivos. Para obter mais informações, consulte Carregar arquivos no
ASP.NET Core.

Use o InputFile componente para ler os dados do arquivo do navegador no código


.NET. O InputFile componente renderiza um elemento HTML <input> do tipo file . Por
padrão, o usuário seleciona arquivos individuais. Adicione o multiple atributo para
permitir que o usuário carregue vários arquivos ao mesmo tempo.

A seleção de arquivo não é cumulativa ao usar um InputFile componente ou seu HTML


<input type="file"> subjacente, portanto, você não pode adicionar arquivos a uma
seleção de arquivo existente. O componente sempre substitui a seleção de arquivo
inicial do usuário, portanto, as referências de arquivo de seleções anteriores não estão
disponíveis.

InputFile O componente a seguir executa o LoadFiles método quando o OnChange


evento (change ) ocorre. Um InputFileChangeEventArgs fornece acesso à lista de
arquivos selecionada e detalhes sobre cada arquivo:

razor

<InputFile OnChange="@LoadFiles" multiple />

@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}

HTML renderizado:

HTML

<input multiple="" type="file" _bl_2="">


7 Observação

No exemplo anterior, o <input> atributo do _bl_2 elemento é usado para Blazoro


processamento interno.

Para ler dados de um arquivo selecionado pelo usuário, chame


IBrowserFile.OpenReadStream no arquivo e leia do fluxo retornado. Para obter mais
informações, consulte a seção Fluxos de arquivos .

OpenReadStream impõe um tamanho máximo em bytes de seu Stream. Ler um arquivo


ou vários arquivos com mais de 500 KB resulta em uma exceção. Esse limite impede que
os desenvolvedores leiam acidentalmente arquivos grandes na memória. O
maxAllowedSize parâmetro de OpenReadStream pode ser usado para especificar um
tamanho maior, se necessário.

Se você precisar de acesso a um Stream que represente os bytes do arquivo, use


IBrowserFile.OpenReadStream. Evite ler o fluxo de arquivos de entrada diretamente na
memória de uma só vez. Por exemplo, não copie todos os bytes do arquivo em um
MemoryStream ou leia todo o fluxo em uma matriz de bytes de uma só vez. Essas
abordagens podem resultar em problemas de desempenho e segurança, especialmente
para Blazor Server aplicativos. Em vez disso, considere adotar uma das seguintes
abordagens:

No servidor de um aplicativo hospedado Blazor WebAssembly ou de um Blazor


Server aplicativo, copie o fluxo diretamente para um arquivo em disco sem lê-lo na
memória. Observe que Blazor os aplicativos não podem acessar diretamente o
sistema de arquivos do cliente.
Carregue arquivos do cliente diretamente para um serviço externo. Para obter mais
informações, consulte a seção Carregar arquivos em um serviço externo .

Nos exemplos a seguir, browserFile representa o arquivo carregado e implementa


IBrowserFile. As implementações de trabalho para IBrowserFile são mostradas nos
FileUpload1 componentes e FileUpload2 posteriormente neste artigo.

❌ A abordagem a seguir NÃO é recomendada porque o conteúdo do Stream arquivo


é lido em um String na memória ( reader ):

C#

var reader =
await new StreamReader(browserFile.OpenReadStream()).ReadToEndAsync();
❌ A abordagem a seguir NÃO é recomendada para o Microsoft Armazenamento de
Blobs do Azure porque o conteúdo do Stream arquivo é copiado em um MemoryStream
na memória ( memoryStream ) antes de chamar UploadBlobAsync:

C#

var memoryStream = new MemoryStream();


browserFile.OpenReadStream().CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
trustedFilename, memoryStream));

✔️a seguinte abordagem é recomendada porque o do Stream arquivo é fornecido


diretamente ao consumidor, um FileStream que cria o arquivo no caminho fornecido:

C#

await using FileStream fs = new(path, FileMode.Create);


await browserFile.OpenReadStream().CopyToAsync(fs);

✔️a seguinte abordagem é recomendada para o Microsoft Armazenamento de Blobs


do Azure porque o do Stream arquivo é fornecido diretamente para UploadBlobAsync:

C#

await blobContainerClient.UploadBlobAsync(
trustedFilename, browserFile.OpenReadStream());

Um componente que recebe um arquivo de imagem pode chamar o


BrowserFileExtensions.RequestImageFileAsync método de conveniência no arquivo para
redimensionar os dados da imagem no runtime javaScript do navegador antes que a
imagem seja transmitida para o aplicativo. Os casos de uso para chamadas
RequestImageFileAsync são mais apropriados para Blazor WebAssembly aplicativos.

O exemplo a seguir demonstra o upload de vários arquivos em um componente.


InputFileChangeEventArgs.GetMultipleFiles permite a leitura de vários arquivos.
Especifique o número máximo de arquivos para impedir que um usuário mal-
intencionado carregue um número maior de arquivos do que o aplicativo espera.
InputFileChangeEventArgs.File permite a leitura do primeiro e único arquivo se o upload
de arquivo não der suporte a vários arquivos.

7 Observação

InputFileChangeEventArgs está no Microsoft.AspNetCore.Components.Forms


namespace, que normalmente é um dos namespaces no arquivo do
_Imports.razor aplicativo. Quando o namespace está presente no _Imports.razor

arquivo, ele fornece acesso de membro da API aos componentes do aplicativo:

razor

using Microsoft.AspNetCore.Components.Forms

Namespaces no _Imports.razor arquivo não são aplicados a arquivos C# ( .cs ). Os


arquivos C# exigem uma diretiva explícita using .

7 Observação

Para testar componentes de carregamento de arquivos, você pode criar arquivos de


teste de qualquer tamanho com o PowerShell:

PowerShell

$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out);


[IO.File]::WriteAllBytes('{PATH}', $out)

No comando anterior:

O {SIZE} espaço reservado é o tamanho do arquivo em bytes (por exemplo,


2097152 para um arquivo de 2 MB).

O {PATH} espaço reservado é o caminho e o arquivo com a extensão de


arquivo (por exemplo, D:/test_files/testfile2MB.txt ).

Pages/FileUpload1.razor :

razor

@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment

<h3>Upload Files</h3>

<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>

<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>

<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
</p>

@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}

@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;

private async Task LoadFiles(InputFileChangeEventArgs e)


{
isLoading = true;
loadedFiles.Clear();

foreach (var file in e.GetMultipleFiles(maxAllowedFiles))


{
try
{
loadedFiles.Add(file);
var trustedFileNameForFileStorage =
Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);

await using FileStream fs = new(path, FileMode.Create);


await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}

isLoading = false;
}
}

IBrowserFile retorna metadados expostos pelo navegador como propriedades. Use


esses metadados para validação preliminar.

2 Aviso

Nunca confie nos valores das propriedades a seguir, especialmente na Name


propriedade para exibição na interface do usuário. Trate todos os dados fornecidos
pelo usuário como um risco de segurança significativo para o aplicativo, o servidor
e a rede. Para obter mais informações, consulte Carregar arquivos no ASP.NET
Core.

Name
Size
LastModified
ContentType

Carregar arquivos em um servidor


O exemplo a seguir demonstra o carregamento de arquivos de um Blazor Server
aplicativo para um controlador de API Web de back-end em um aplicativo separado,
possivelmente em um servidor separado.

Blazor Server No aplicativo, adicione IHttpClientFactory e serviços relacionados que


permitem que o aplicativo crie HttpClient instâncias.
Em Program.cs :

C#

builder.Services.AddHttpClient();

Para saber mais, confira Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET
Core.

Para os exemplos nesta seção:

A API Web é executada na URL: https://localhost:5001


O Blazor Server aplicativo é executado na URL: https://localhost:5003

Para teste, as URLs anteriores são configuradas nos arquivos dos


Properties/launchSettings.json projetos.

Carregar classe de resultado


A classe a seguir UploadResult é colocada no projeto do cliente e no projeto da API
Web para manter o resultado de um arquivo carregado. Quando um arquivo falha ao
carregar no servidor, um código de erro é retornado ErrorCode para exibição para o
usuário. Um nome de arquivo seguro é gerado no servidor para cada arquivo e
retornado ao cliente em StoredFileName para exibição. Os arquivos são chaveados entre
o cliente e o servidor usando o nome de arquivo não confiável/não confiável no
FileName .

UploadResult.cs :

C#

public class UploadResult


{
public bool Uploaded { get; set; }
public string? FileName { get; set; }
public string? StoredFileName { get; set; }
public int ErrorCode { get; set; }
}

7 Observação

Uma prática recomendada de segurança para aplicativos de produção é evitar o


envio de mensagens de erro para clientes que possam revelar informações
confidenciais sobre um aplicativo, servidor ou rede. Fornecer mensagens de erro
detalhadas pode ajudar um usuário mal-intencionado a criar ataques em um
aplicativo, servidor ou rede. O código de exemplo nesta seção envia apenas um
número de código de erro ( int ) para exibição pelo lado do cliente do componente
se ocorrer um erro no lado do servidor. Se um usuário precisar de assistência com
um upload de arquivo, ele fornecerá o código de erro para dar suporte à equipe
para resolução de tíquetes de suporte sem nunca saber a causa exata do erro.

Carregar componente
O seguinte FileUpload2 componente:

Permite que os usuários carreguem arquivos do cliente.


Exibe o nome de arquivo não confiável/não seguro fornecido pelo cliente na
interface do usuário. O nome do arquivo não confiável/não seguro é codificado
automaticamente por Razor HTML para exibição segura na interface do usuário.

2 Aviso

Não confie em nomes de arquivo fornecidos pelos clientes para:

Salvando o arquivo em um sistema ou serviço de arquivos.


Exibe em UIs que não codificam nomes de arquivo automaticamente ou por
meio do código do desenvolvedor.

Para obter mais informações sobre considerações de segurança ao carregar


arquivos em um servidor, consulte Carregar arquivos em ASP.NET Core.

Pages/FileUpload2.razor no Blazor Server aplicativo:

razor

@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
</p>

@if (files.Count > 0)


{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}

@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;

protected override bool ShouldRender() => shouldRender;

private async Task OnInputFileChange(InputFileChangeEventArgs e)


{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;

using var content = new MultipartFormDataContent();

foreach (var file in e.GetMultipleFiles(maxAllowedFiles))


{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });

var fileContent =
new StreamContent(file.OpenReadStream(maxFileSize));

fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);

content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);

upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);

uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}

if (upload)
{
var client = ClientFactory.CreateClient();

var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);

if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};

using var responseStream =


await response.Content.ReadAsStreamAsync();

var newUploadResults = await JsonSerializer


.DeserializeAsync<IList<UploadResult>>(responseStream,
options);

if (newUploadResults is not null)


{
uploadResults =
uploadResults.Concat(newUploadResults).ToList();
}
}
}

shouldRender = true;
}

private static bool FileUpload(IList<UploadResult> uploadResults,


string? fileName, ILogger<FileUpload2> logger, out UploadResult
result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName)
?? new();

if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)",
fileName);
result.ErrorCode = 5;
}

return result.Uploaded;
}

private class File


{
public string? Name { get; set; }
}
}

Carregar controlador
O controlador a seguir no projeto de API Web salva arquivos carregados do cliente.

) Importante

O controlador nesta seção destina-se a ser usado em um projeto de API Web


separado do Blazor Server aplicativo.

Para usar o código a seguir, crie uma Development/unsafe_uploads pasta na raiz do


projeto de API Web para o aplicativo em execução no Development ambiente.
Como o exemplo usa o ambiente do aplicativo como parte do caminho em que os
arquivos são salvos, pastas adicionais são necessárias se outros ambientes forem usados
em teste e produção. Por exemplo, crie uma Staging/unsafe_uploads pasta para o
Staging ambiente. Crie uma Production/unsafe_uploads pasta para o Production
ambiente.

2 Aviso

O exemplo salva arquivos sem verificar seu conteúdo e as diretrizes neste artigo
não levam em conta práticas recomendadas de segurança adicionais para arquivos
carregados. Em sistemas de preparo e produção, desabilite a permissão de
execução na pasta de upload e verifique arquivos com uma API de scanner
antivírus/antimalware imediatamente após o upload. Para obter mais informações,
consulte Carregar arquivos no ASP.NET Core.

Controllers/FilesaveController.cs :

C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
{
private readonly IWebHostEnvironment env;
private readonly ILogger<FilesaveController> logger;

public FilesaveController(IWebHostEnvironment env,


ILogger<FilesaveController> logger)
{
this.env = env;
this.logger = logger;
}

[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = new();

foreach (var file in files)


{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);

if (filesProcessed < maxAllowedFiles)


{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is "
+
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length,
maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage =
Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);

await using FileStream fs = new(path,


FileMode.Create);
await file.CopyToAsync(fs);

logger.LogInformation("{FileName} saved at {Path}",


trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName =
trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err:
3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}

filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the "
+
"request exceeded the allowed {Count} of files (Err:
4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}

uploadResults.Add(uploadResult);
}

return new CreatedResult(resourcePath, uploadResults);


}
}

No código anterior, GetRandomFileName é chamado para gerar um nome de arquivo


seguro. Nunca confie no nome do arquivo fornecido pelo navegador, pois um invasor
pode escolher um nome de arquivo existente que substitua um arquivo existente ou
envie um caminho que tente gravar fora do aplicativo.

Cancelar um upload de arquivo


Um componente de upload de arquivo pode detectar quando um usuário cancelou um
upload usando um CancellationToken ao chamar para o IBrowserFile.OpenReadStream
ou StreamReader.ReadAsync.

Crie um CancellationTokenSource para o InputFile componente. No início do


OnInputFileChange método, verifique se um upload anterior está em andamento.

Se um upload de arquivo estiver em andamento:

Chame Cancel no upload anterior.


Crie um novo CancellationTokenSource para o próximo upload e passe o
CancellationTokenSource.Token para OpenReadStream ou ReadAsync.

Carregar arquivos com progresso


O exemplo a seguir demonstra como carregar arquivos em um Blazor Server aplicativo
com o progresso do upload exibido para o usuário.

Para usar o exemplo a seguir em um aplicativo de teste:

Crie uma pasta para salvar arquivos carregados para o Development ambiente:
Development/unsafe_uploads .

Configure o tamanho máximo do arquivo ( maxFileSize , 15 KB no exemplo a


seguir) e o número máximo de arquivos permitidos ( maxAllowedFiles , 3 no
exemplo a seguir).
Defina o buffer como um valor diferente (10 KB no exemplo a seguir), se desejado,
para aumentar a granularidade no relatório em andamento. Não recomendamos
usar um buffer maior que 30 KB devido a problemas de desempenho e segurança.

2 Aviso

O exemplo salva arquivos sem verificar seu conteúdo e as diretrizes neste artigo
não levam em conta práticas recomendadas de segurança adicionais para arquivos
carregados. Em sistemas de preparo e produção, desabilite a permissão de
execução na pasta de upload e verifique arquivos com uma API de scanner
antivírus/antimalware imediatamente após o upload. Para obter mais informações,
consulte Carregar arquivos no ASP.NET Core.

Pages/FileUpload3.razor :

razor

@page "/file-upload-3"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment

<h3>Upload Files</h3>

<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>

<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>

<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
</p>

@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}

@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;

private async Task LoadFiles(InputFileChangeEventArgs e)


{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;

foreach (var file in e.GetMultipleFiles(maxAllowedFiles))


{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileName);
await using FileStream writeStream = new(path,
FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];

while ((bytesRead = await readStream.ReadAsync(buffer)) !=


0)
{
totalRead += bytesRead;

await writeStream.WriteAsync(buffer, 0, bytesRead);

progressPercent = Decimal.Divide(totalRead, file.Size);

StateHasChanged();
}

loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}

isLoading = false;
}
}

Para obter mais informações, consulte os seguintes recursos de API:

FileStream: fornece um Stream para um arquivo, dando suporte a operações de


leitura e gravação síncronas e assíncronas.
FileStream.ReadAsync: o componente anterior FileUpload3 lê o fluxo de forma
assíncrona com ReadAsync. Não há suporte para a leitura de um fluxo de forma
síncrona com Read os Razor componentes.

Fluxos de arquivos
No Blazor Server, os dados do arquivo são transmitidos pela conexão para o SignalR
código .NET no servidor à medida que o arquivo é lido.

Pré-visualização de imagem de upload


Para uma visualização de imagem do carregamento de imagens, comece adicionando
um InputFile componente com uma referência de componente e um OnChange
manipulador:

razor

<InputFile @ref="inputFile" OnChange="@ShowPreview" />

Adicione um elemento de imagem com uma referência de elemento, que serve como o
espaço reservado para a visualização da imagem:

razor

<img @ref="previewImageElem" />

Adicione as referências associadas:

razor

@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
}

No JavaScript, adicione uma função chamada com um HTML input e img um


elemento que executa o seguinte:

Extrai o arquivo selecionado.


Cria uma URL de objeto com createObjectURL .
Define um ouvinte de eventos para revogar a URL do objeto com depois que
revokeObjectURL a imagem é carregada, para que a memória não seja vazada.
Define a origem img do elemento para exibir a imagem.

JavaScript

function previewImage(inputElem, imgElem) {


const url = URL.createObjectURL(inputElem.files[0]);
imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once:
true });
imgElem.src = url;
}

Por fim, use um injetado IJSRuntime para adicionar o OnChange manipulador que chama
a função JavaScript:
razor

@inject IJSRuntime JS

...

@code {
private ValueTask ShowPreview()
=> JS.InvokeVoidAsync("previewImage", inputFile!.Element,
previewImageElem);
}

O exemplo anterior é para carregar uma única imagem. A abordagem pode ser
expandida para dar suporte multiple a imagens.

Carregar arquivos em um serviço externo


Em vez de um arquivo de tratamento de aplicativo carregar bytes e o servidor do
aplicativo receber arquivos carregados, os clientes podem carregar arquivos
diretamente em um serviço externo. O aplicativo pode processar com segurança os
arquivos do serviço externo sob demanda. Essa abordagem protege o aplicativo e seu
servidor contra ataques mal-intencionados e possíveis problemas de desempenho.

Considere uma abordagem que usa Arquivos do Azure , Armazenamento de Blobs do


Azure ou um serviço de terceiros com os seguintes benefícios potenciais:

Carregue arquivos do cliente diretamente para um serviço externo com uma API
ou REST biblioteca de clientes JavaScript. Por exemplo, o Azure oferece as
seguintes APIs e bibliotecas de cliente:
Biblioteca de clientes do Compartilhamento de Arquivos do Armazenamento do
Azure
REST API Arquivos do Azure
Biblioteca de cliente do Azure Storage Blob para JavaScript
API do serviço REST Blob
Autorize uploads de usuário com um token SAS (assinatura de acesso
compartilhado) delegado pelo usuário gerado pelo aplicativo (lado do servidor)
para cada upload de arquivo cliente. Por exemplo, o Azure oferece os seguintes
recursos de SAS:
Biblioteca de clientes do Compartilhamento de Arquivos do Armazenamento do
Azure para JavaScript: com token SAS
Biblioteca de clientes do Blob de Armazenamento do Azure para JavaScript:
com token SAS
Forneça redundância automática e backup de compartilhamento de arquivos.
Limite os uploads com cotas. Observe que as cotas do Armazenamento de Blobs
do Azure são definidas no nível da conta, não no nível do contêiner. No entanto,
Arquivos do Azure cotas estão no nível de compartilhamento de arquivos e podem
fornecer melhor controle sobre os limites de carregamento. Para obter mais
informações, consulte os documentos do Azure vinculados anteriormente nesta
lista.
Proteger arquivos com SSE (criptografia do lado do servidor).

Para obter mais informações sobre Armazenamento de Blobs do Azure e Arquivos do


Azure, consulte a documentação do Armazenamento do Azure.

SignalR limite de tamanho da mensagem


Os uploads de arquivo podem falhar antes mesmo de serem iniciados, quando Blazor
recuperam dados sobre os arquivos que excedem o tamanho máximo SignalR da
mensagem.

SignalR define um limite de tamanho de mensagem que se aplica a cada mensagem


Blazor recebida e o InputFile componente transmite arquivos para o servidor em
mensagens que respeitam o limite configurado. No entanto, a primeira mensagem, que
indica o conjunto de arquivos a serem carregados, é enviada como uma única
mensagem exclusiva. O tamanho da primeira mensagem pode exceder o limite de
tamanho da SignalR mensagem. O problema não está relacionado ao tamanho dos
arquivos, está relacionado ao número de arquivos.

O erro registrado é semelhante ao seguinte:

Erro: conexão desconectada com o erro 'Erro: o servidor retornou um erro ao fechar:
conexão fechada com um erro.'. e.log @ blazor.server.js:1

Ao carregar arquivos, atingir o limite de tamanho da mensagem na primeira mensagem


é raro. Se o limite for atingido, o aplicativo poderá configurar
HubOptions.MaximumReceiveMessageSize com um valor maior.

Para obter mais informações sobre SignalR a configuração e como definir


MaximumReceiveMessageSize, consulte ASP.NET Core BlazorSignalR diretrizes.

Recursos adicionais
Blazor ASP.NET Core downloads de arquivo
Carregar arquivos no ASP.NET Core
Blazor ASP.NET Core formulários e componentes de entrada
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Blazor ASP.NET Core downloads de
arquivo
Artigo • 28/11/2022 • 14 minutos para o fim da leitura

Este artigo explica como baixar arquivos em Blazor Server aplicativos e Blazor
WebAssembly .

Os arquivos podem ser baixados dos próprios ativos estáticos do aplicativo ou de


qualquer outro local:

ASP.NET Core aplicativos usam o Middleware de Arquivo Estático para atender


arquivos a clientes de Blazor Server aplicativos hospedados Blazor WebAssembly e.
As diretrizes neste artigo também se aplicam a outros tipos de servidores de
arquivos que não usam o .NET, como CDNs (Redes de Distribuição de Conteúdo).

Este artigo aborda abordagens para os seguintes cenários:

Transmitir conteúdo do arquivo para um buffer de dados binário bruto no cliente:


normalmente, essa abordagem é usada para arquivos relativamente pequenos (<
250 MB).
Baixe um arquivo por meio de uma URL sem streaming: geralmente, essa
abordagem é usada para arquivos relativamente grandes (> 250 MB).

Ao baixar arquivos de uma origem diferente do aplicativo, as considerações sobre o


CORS (Compartilhamento de Recursos entre Origens) se aplicam. Para obter mais
informações, consulte a seção CORS (Compartilhamento de Recursos entre Origens).

Considerações de segurança
Tenha cuidado ao fornecer aos usuários a capacidade de baixar arquivos de um servidor.
Os invasores podem executar ataques de DOS (negação de serviço),ataques de
exploração de API ou tentar comprometer redes e servidores de outras maneiras.

As etapas de segurança que reduzem a probabilidade de um ataque bem-sucedido são:

Baixe arquivos de uma área de download de arquivo dedicado no servidor,


preferencialmente de uma unidade que não seja do sistema. O uso de um local
dedicado facilita a imposição de restrições de segurança a arquivos baixáveis.
Desabilite as permissões de execução na área de download de arquivos.
As verificações de segurança do lado do cliente são fáceis de contornar por
usuários mal-intencionados. Sempre execute verificações de segurança do lado do
cliente no servidor também.
Não receba arquivos de usuários ou outras fontes não confiáveis e disponibilize os
arquivos para download imediato sem executar verificações de segurança nos
arquivos. Para obter mais informações, consulte Carregar arquivos no ASP.NET
Core.

Download de um fluxo
Esta seção se aplica a arquivos que normalmente têm até 250 MB de tamanho.

A abordagem recomendada para baixar arquivos relativamente pequenos (<250 MB) é


transmitir o conteúdo do arquivo para um buffer de dados binário bruto no cliente com
interoperabilidade JavaScript (JS).

2 Aviso

A abordagem nesta seção lê o conteúdo do arquivo em um JS ArrayBuffer . Essa


abordagem carrega todo o arquivo na memória do cliente, o que pode prejudicar o
desempenho. Para baixar arquivos relativamente grandes (>= 250 MB),
recomendamos seguir as diretrizes na seção Baixar de uma URL .

A função a seguir downloadFileFromStream JS executa as seguintes etapas:

Leia o fluxo fornecido em um ArrayBuffer .


Crie um Blob para encapsular o ArrayBuffer .
Crie uma URL de objeto para servir como o endereço de download do arquivo.
Criar um HTMLAnchorElement elemento ( <a> ).
Atribua o nome do arquivo ( fileName ) e a URL ( url ) para o download.
Dispare o download disparando um click evento no elemento anchor.
Remova o elemento de âncora.
Revogue a URL do objeto ( url ) chamando URL.revokeObjectURL . Essa é uma
etapa importante para garantir que a memória não seja vazada no cliente.

HTML

<script>
window.downloadFileFromStream = async (fileName, contentStreamReference)
=> {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
URL.revokeObjectURL(url);
}
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).

O seguinte componente de exemplo:

Usa a interoperabilidade de streaming de bytes nativa para garantir a transferência


eficiente do arquivo para o cliente.
Tem um método chamado GetFileStream para recuperar um Stream para o
arquivo baixado para clientes. Abordagens alternativas incluem recuperar um
arquivo do armazenamento ou gerar um arquivo dinamicamente no código C#.
Para essa demonstração, o aplicativo cria um arquivo de 50 KB de dados aleatórios
de uma nova matriz de bytes ( new byte[] ). Os bytes são encapsulados com um
MemoryStream para servir como o arquivo binário gerado dinamicamente do
exemplo.
O DownloadFileFromStream método executa as seguintes etapas:
Recupere o Stream de GetFileStream .
Especifique um nome de arquivo quando o arquivo for salvo no computador do
usuário. O exemplo a seguir nomeia o arquivo quote.txt .
Encapsula o Stream em um DotNetStreamReference, que permite transmitir os
dados do arquivo para o cliente.
Invoque a downloadFileFromStream JS função para aceitar os dados no cliente.

razor

@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<h1>File Download Example</h1>

<button @onclick="DownloadFileFromStream">
Download File From Stream
</button>
@code {
private Stream GetFileStream()
{
var randomBinaryData = new byte[50 * 1024];
var fileStream = new MemoryStream(randomBinaryData);

return fileStream;
}

private async Task DownloadFileFromStream()


{
var fileStream = GetFileStream();
var fileName = "log.bin";

using var streamRef = new DotNetStreamReference(stream: fileStream);

await JS.InvokeVoidAsync("downloadFileFromStream", fileName,


streamRef);
}
}

Para um componente em um Blazor Server aplicativo que deve retornar um Stream para
um arquivo físico, o componente pode chamar File.OpenRead, como demonstra o
exemplo a seguir:

C#

private Stream GetFileStream()


{
return File.OpenRead(@"{PATH}");
}

No exemplo anterior, o {PATH} espaço reservado é o caminho para o arquivo. O @


prefixo indica que a cadeia de caracteres é um literal de cadeia de caracteres textual,
que permite o uso de barras invertidas ( \ ) em um caminho do sistema operacional
Windows e aspas duplas inseridas ( "" ) para uma única aspa no caminho. Como
alternativa, evite o literal da cadeia de caracteres ( @ ) e use uma das seguintes
abordagens:

Use barras invertidas de escape ( \\ ) e aspas ( \" ).


Use barras / () no caminho, que são compatíveis entre plataformas em aplicativos
ASP.NET Core e aspas de escape ( \" ).

Baixar de uma URL


Esta seção se aplica a arquivos relativamente grandes, normalmente de 250 MB ou
maiores.

O exemplo nesta seção usa um arquivo de download chamado quote.txt , que é


colocado em uma pasta chamada files na raiz da Web do aplicativo ( wwwroot pasta). O
uso da files pasta é apenas para fins de demonstração. Você pode organizar arquivos
baixáveis em qualquer layout de pasta dentro da raiz da Web ( wwwroot pasta) que
preferir, incluindo o fornecimento dos arquivos diretamente da wwwroot pasta.

wwwroot/files/quote.txt :

text

When victory is ours, we'll wipe every trace of the Thals and their city
from the face of this land. We will avenge the deaths of all Kaleds who've
fallen in the cause of right and justice and build a peace which will be a
monument to their sacrifice. Our battle cry will be "Total extermination of
the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)


Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00pq2gc)
©1975 BBC (https://www.bbc.co.uk/)

A função a seguir triggerFileDownload JS executa as seguintes etapas:

Criar um HTMLAnchorElement elemento ( <a> ).


Atribua o nome do arquivo ( fileName ) e a URL ( url ) para o download.
Dispare o download disparando um click evento no elemento anchor.
Remova o elemento de âncora.

HTML

<script>
window.triggerFileDownload = (fileName, url) => {
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
}
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).

O componente de exemplo a seguir baixa o arquivo da mesma origem que o aplicativo


usa. Se o download do arquivo for tentado de uma origem diferente, configure CORS
(Compartilhamento de Recursos entre Origens). Para obter mais informações, consulte a
seção CORS (Compartilhamento de Recursos entre Origens).

razor

@page "/file-download-2"
@inject IJSRuntime JS

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
Download File From URL
</button>

@code {
private async Task DownloadFileFromURL()
{
var fileName = "quote.txt";
var fileURL = "https://localhost:5001/files/quote.txt";
await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
}
}

CORS (Compartilhamento de Recursos entre


Origens)
Sem executar outras etapas para habilitar o CORS (Compartilhamento de Recursos entre
Origens) para arquivos que não têm a mesma origem que o aplicativo, baixar arquivos
não passará por verificações cors feitas pelo navegador.

Para obter mais informações sobre o CORS com aplicativos ASP.NET Core e outros
produtos e serviços da Microsoft que hospedam arquivos para download, consulte os
seguintes recursos:

Habilitar o CORS (solicitações entre origens) no ASP.NET Core


Usando a CDN do Azure com CORS (documentação do Azure)
Suporte ao CORS (Compartilhamento de Recursos entre Origens) para o
Armazenamento do Azure (REST documentação)
Core Serviços de Nuvem – Configurar o CORS para seu site e ativos de
armazenamento (módulo Learn)
Referência de configuração do módulo CORS do IIS (documentação do IIS)

Recursos adicionais
Blazor ASP.NET Core interoperabilidade do JavaScript (JSinteroperabilidade)
<a>: o elemento Anchor: Segurança e privacidade (documentação do MDN)
Blazor carregamentos de arquivo ASP.NET Core
Blazor ASP.NET Core formulários e componentes de entrada
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Interoperabilidade de JavaScript e
ASP.NET Core Blazor (interoperabilidade
de JS)
Artigo • 26/09/2022 • 29 minutos para o fim da leitura

Este artigo explica conceitos gerais sobre como interagir com JavaScript em aplicativos
Blazor.

Um aplicativo Blazor pode invocar funções JavaScript (JS) de métodos .NET e métodos
.NET de funções JS. Esses cenários são chamados de interoperabilidade entre e
JavaScript (interoperabilidade de JS).

Outras diretrizes de interoperabilidade de JS são fornecidas nos seguintes artigos:

Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor


Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor

Interação com o DOM (Modelo de Objeto do


Documento)
Altere o DOM (Modelo de Objeto do Documento) com JavaScript (JS) somente quando
o objeto não interagir com Blazor. Blazor mantém representações do DOM e interage
diretamente com objetos DOM. Se um elemento renderizado por Blazor for modificado
externamente usando JS diretamente ou via interoperabilidade com JS, o DOM poderá
não corresponder mais à representação interna de Blazor, o que poderá resultar em um
comportamento indefinido. O comportamento indefinido pode apenas interferir na
apresentação de elementos ou de suas funções, mas também pode introduzir riscos de
segurança ao aplicativo ou servidor.

Essa diretriz não se aplica apenas ao seu código de interoperabilidade de JS, mas
também a todas as bibliotecas JS que o aplicativo usa, incluindo qualquer coisa
fornecida por uma estrutura de terceiros, como Bootstrap JS e jQuery .

Em alguns exemplos de documentação, a interoperabilidade de JS é usada para alterar


um elemento puramente para fins de demonstração como parte de um exemplo. Nesses
casos, um aviso aparece no texto.

Para obter mais informações, consulte Chamar funções JavaScript de métodos .NET no
ASP.NET Core Blazor.
Chamadas de JavaScript assíncronas
As chamadas de interoperabilidade de JS são assíncronas por padrão,
independentemente do código chamado ser síncrono ou assíncrono. As chamadas são
assíncronas por padrão para garantir que os componentes sejam compatíveis entre os
modelos de hospedagem de Blazor, Blazor Server e Blazor WebAssembly. Em Blazor
Server, as chamadas de interoperabilidade de JS devem ser assíncronas porque são
enviadas por uma conexão de rede. Para aplicativos que adotam exclusivamente o
modelo de hospedagem de Blazor WebAssembly, há suporte para chamadas de
interoperabilidade de JS síncronas.

Para obter mais informações, consulte os seguintes artigos:

Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor


Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor

Serialização de objeto
Blazor usa System.Text.Json para serialização com os seguintes requisitos e
comportamentos padrão:

Os tipos devem ter um construtor padrão, get/set os acessadores de devem ser


públicos e os campos nunca são serializados.
A serialização padrão global não é personalizável para evitar a quebra de
bibliotecas de componentes existentes, impactos sobre o desempenho e a
segurança e reduções na confiabilidade.
A serialização de nomes de membros do .NET resulta em nomes de chave JSON
minúsculos.
O JSON é desserializado como instâncias de C# JsonElement, que permitem a
combinação de maiúsculas e minúsculas. A conversão interna para atribuição a
propriedades do modelo C# funciona conforme o esperado, apesar de eventuais
diferenças de maiúsculas e minúsculas entre nomes de chave JSON e nomes de
propriedades C#.

A API JsonConverter está disponível para serialização personalizada. As propriedades


podem ser anotadas com um atributo [JsonConverter] para substituir a serialização
padrão para um tipo de dados existente.

Para obter mais informações, consulte os seguintes recursos na documentação do .NET:

Serialização e desserialização de JSON (marshalling e unmarshalling) no .NET


Como personalizar nomes e valores de propriedade com System.Text.Json
Como gravar conversores personalizados para serialização de JSON (marshalling)
no .NET

O suporte ao serializador de JSON para System.DateOnly e System.TimeOnly está


previsto para o ASP.NET Core 7.0, que está programado para lançamento até o final de
2022. Para obter mais informações, consulte Suporte para DateOnly e TimeOnly em
JsonSerializer (dotnet/runtime #53539) .

Blazor dá suporte à interoperabilidade de JS da matriz de bytes otimizada que evita


codificar/decodificar matrizes de bytes em Base64. O aplicativo pode aplicar a
serialização personalizada e passar os bytes resultantes. Para obter mais informações,
consulte Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor.

Blazor dá suporte à interoperabilidade de JS sem marshaling quando um grande volume


de objetos .NET é serializado rapidamente ou quando objetos .NET grandes ou muitos
objetos .NET devem ser serializados. Para obter mais informações, consulte Chamar
funções JavaScript de métodos .NET no ASP.NET Core Blazor.

Inicializadores de JavaScript
Inicializadores de JavaScript (JS) executam a lógica antes e depois do carregamento de
um aplicativo Blazor. Inicializadores de JS são úteis nos seguintes cenários:

Personalizar como um aplicativo Blazor é carregado.


Inicializar bibliotecas antes de iniciar Blazor.
Definir configurações de Blazor.

Os inicializadores de JS são detectados como parte do processo de build e importados


automaticamente em aplicativos Blazor. O uso de inicializadores de JS geralmente
remove a necessidade de disparar manualmente funções de script do aplicativo ao usar
RCLs (bibliotecas de classes) de Razor.

Para definir um inicializador de JS, adicione um módulo JS ao projeto chamado


{NAME}.lib.module.js , em que o espaço reservado {NAME} é o nome do assembly, o
nome da biblioteca ou o identificador do pacote. Coloque o arquivo na raiz da Web do
projeto, que normalmente é a pasta wwwroot .

O módulo exporta uma das seguintes funções convencionais, ou ambas:

beforeStart(options, extensions) : chamado antes de Blazor começar. Por

exemplo, beforeStart é usado para personalizar o processo de carregamento, o


nível de registros em log e outras opções específicas para o modelo de
hospedagem.
Em Blazor WebAssembly, beforeStart recebe as opções de Blazor
WebAssembly ( options no exemplo desta seção) e de extensões ( extensions
no exemplo desta seção) adicionadas durante a publicação. Por exemplo, as
opções podem especificar o uso de um carregador de recursos de inicialização
personalizado.
Em Blazor Server, beforeStart recebe opções de início de circuito de SignalR
( options no exemplo desta seção).
Em BlazorWebViews, nenhuma opção é passada.
afterStarted : chamado após Blazor estar pronto para receber chamadas de JS. Por
exemplo, afterStarted é usado para inicializar bibliotecas fazendo chamadas de
interoperabilidade de JS e registrando elementos personalizados. A instância de
Blazor é passada para afterStarted como um argumento ( blazor no exemplo
desta seção).

O exemplo a seguir demonstra inicializadores de JS para beforeStart e afterStarted .


Para o nome do arquivo do seguinte exemplo:

Use o nome do assembly do aplicativo no nome do arquivo se os inicializadores de


JS forem consumidos como um ativo estático no projeto. Por exemplo, nomeie o
arquivo BlazorSample.lib.module.js para um projeto com o nome de assembly
BlazorSample . Coloque o arquivo na pasta wwwroot do aplicativo.
Use o nome da biblioteca ou o identificador do pacote do projeto se os
inicializadores JS forem consumidos de uma RCL. Por exemplo, nomeie o arquivo
RazorClassLibrary1.lib.module.js para uma RCL com um identificador de pacote

RazorClassLibrary1 . Coloque o arquivo na pasta wwwroot da biblioteca.

JavaScript

export function beforeStart(options, extensions) {


console.log("beforeStart");
}

export function afterStarted(blazor) {


console.log("afterStarted");
}

7 Observação

Aplicativos MVC e Razor Pages não carregam inicializadores JS automaticamente.


No entanto, o código do desenvolvedor pode incluir um script para buscar o
manifesto do aplicativo e disparar a carga dos inicializadores JS.
Para ver exemplos de inicializadores JS, consulte os seguintes recursos:

Layout de implantação para aplicativos Blazor WebAssembly ASP.NET Core


Aplicativo de Teste Básico no repositório do GitHub do ASP.NET Core
(BasicTestApp.lib.module.js)

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Local do JavaScript
Carregue o código JavaScript (JS) usando qualquer uma das seguintes abordagens:

Carregar um script na marcação de <head> (geralmente, não recomendado)


Carregar um script na marcação de <body>
Carregar um script de um arquivo JavaScript externo (.js) agrupado com um
componente
Carregar um script de um arquivo JavaScript externo (.js)
Injetar um script após o início de Blazor

2 Aviso

Não coloque uma marca <script> em um arquivo de componente Razor ( .razor )


porque a marca <script> não pode ser atualizada dinamicamente por Blazor.

7 Observação

Exemplos de documentação geralmente colocam scripts em uma marca <script>


ou carregam scripts globais de arquivos externos. Essas abordagens poluem o
cliente com funções globais. Para aplicativos de produção, recomendamos colocar
o JavaScript em módulos do JavaScript separados que podem ser importados
quando necessário. Para obter mais informações, consulte a seção Isolamento de
JavaScript em módulos de JavaScript.
Carregar um script na marcação de <head>
A abordagem nesta seção não é geralmente recomendada.

Coloque as marcas de JavaScript (JS) ( <script>...</script> ) na marcação do elemento


<head> de wwwroot/index.html (Blazor WebAssembly) ou Pages/_Layout.cshtml (Blazor

Server):

HTML

<head>
...

<script>
window.jsMethod = (methodParameter) => {
...
};
</script>
</head>

O carregamento de JS do <head> não é a melhor abordagem pelos seguintes motivos:

A interoperabilidade de JS poderá falhar se o script depender de Blazor. É


recomendável carregar os scripts usando uma das outras abordagens, não por
meio da marcação de <head> .
A página poderá se tornar interativa mais lentamente devido ao tempo necessário
para analisar o JS no script.

Carregar um script na marcação de <body>


Coloque as marcas de JavaScript (JS) dentro da marcação do elemento <script>...
</script> de fechamento </body> de wwwroot/index.html (Blazor WebAssembly) ou
Pages/_Layout.cshtml (Blazor Server):

HTML

<body>
...

<script src="_framework/blazor.{webassembly|server}.js"></script>
<script>
window.jsMethod = (methodParameter) => {
...
};
</script>
</body>
O espaço reservado {webassembly|server} na marcação anterior é webassembly para um
aplicativo Blazor WebAssembly ( blazor.webassembly.js ) ou server para um aplicativo
Blazor Server ( blazor.server.js ).

Carregar um script de um arquivo JavaScript externo


( .js ) agrupado com um componente
A colocalização de arquivos JavaScript (JS) para páginas, exibições e componentes Razor
é uma maneira conveniente de organizar scripts em um aplicativo.

Colocalize arquivos JS usando as seguintes convenções de extensão de nome de


arquivo:

Páginas de aplicativos Razor Pages e exibições de aplicativos MVC: .cshtml.js .


Exemplos:
Pages/Index.cshtml.js para a página Index de um aplicativo Razor Pages em

Pages/Index.cshtml .

Views/Home/Index.cshtml.js para a exibição Index de um aplicativo MVC em


Views/Home/Index.cshtml .

Componentes Razor de aplicativos Blazor: .razor.js . Exemplo:


Pages/Index.razor.js para o componente Index em Pages/Index.razor .

Arquivos JS colocalizados são endereçáveis publicamente usando o caminho para o


arquivo no projeto:

Páginas, exibições e componentes de um arquivo de scripts colocalizado no


aplicativo:

{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

O espaço reservado {PATH} é o caminho para a página, exibição ou


componente.
O espaço reservado {PAGE, VIEW, OR COMPONENT} é a página, exibição ou
componente.
O espaço reservado {EXTENSION} corresponde à extensão da página, exibição
ou componente, razor ou cshtml .

Exemplo do Razor Pages:

Um arquivo JS para a página Index é colocado na pasta Pages


( Pages/Index.cshtml.js ) ao lado da página Index ( Pages/Index.cshtml ). Na página
Index , o script é referenciado no caminho na pasta Pages :
razor

@section Scripts {
<script src="~/Pages/Index.cshtml.js"></script>
}

Quando o aplicativo é publicado, a estrutura move automaticamente o script para


a raiz da Web. No exemplo anterior, o script é movido para bin\Release\{TARGET
FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js , em que o espaço
reservado {TARGET FRAMEWORK MONIKER} é o TFM (Moniker da Estrutura de Destino).
Nenhuma alteração é necessária para a URL relativa do script na página Index .

Exemplo de Blazor:

Um arquivo JS para o componente Index é colocado na pasta Pages


( Pages/Index.razor.js ) ao lado do componente Index ( Pages/Index.razor ). No
componente Index , o script é referenciado no caminho na pasta Pages . O
exemplo a seguir é baseado em um exemplo mostrado no artigo Chamar funções
JavaScript de métodos .NET no ASP.NET Core Blazor.

Pages/Index.razor.js :

JavaScript

export function showPrompt(message) {


return prompt(message, 'Type anything here');
}

No método OnAfterRenderAsync do componente Index ( Pages/Index.razor ):

razor

module = await JS.InvokeAsync<IJSObjectReference>(


"import", "./Pages/Index.razor.js");

Quando o aplicativo é publicado, a estrutura move automaticamente o script para


a raiz da Web. No exemplo anterior, o script é movido para bin\Release\{TARGET
FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.razor.js , em que o espaço

reservado {TARGET FRAMEWORK MONIKER} é o TFM (Moniker da Estrutura de Destino).


Nenhuma alteração é necessária para a URL relativa do script no componente
Index .

Para scripts fornecidos por uma RCL (biblioteca de classes) Razor:


_content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

O espaço reservado {PACKAGE ID} é o identificador do pacote da RCL (ou nome


da biblioteca para uma biblioteca de classes referenciada pelo aplicativo).
O espaço reservado {PATH} é o caminho para a página, exibição ou
componente. Se um componente Razor estiver localizado na raiz da RCL, o
segmento de linha não será incluído.
O espaço reservado {PAGE, VIEW, OR COMPONENT} é a página, exibição ou
componente.
O espaço reservado {EXTENSION} corresponde à extensão da página, exibição
ou componente, razor ou cshtml .

No seguinte exemplo de aplicativo Blazor:


O identificador do pacote da RCL é AppJS .
Os scripts de um módulo são carregados para o componente Index
( Index.razor ).
O componente Index está na pasta Pages da RCL.

C#

var module = await JS.InvokeAsync<IJSObjectReference>("import",


"./_content/AppJS/Pages/Index.razor.js");

Para obter mais informações sobre RCLs, consulte Consumir componentes Razor do
ASP.NET Core de uma RCL (biblioteca de classes) Razor.

Carregar um script de um arquivo JavaScript externo


( .js )
Coloque as marcas ( <script>...</script> ) JavaScript (JS) com um caminho de origem
de script ( src ) dentro da marca de fechamento </body> após a referência de script
Blazor.

Em wwwroot/index.html (Blazor WebAssembly) ou Pages/_Layout.cshtml (Blazor Server):

HTML

<body>
...

<script src="_framework/blazor.{webassembly|server}.js"></script>
<script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>
O espaço reservado {webassembly|server} na marcação anterior é webassembly para um
aplicativo Blazor WebAssembly ( blazor.webassembly.js ) ou server para um aplicativo
Blazor Server ( blazor.server.js ). O espaço reservado {SCRIPT PATH AND FILE NAME
(.js)} é o caminho e o nome do arquivo de script em wwwroot .

No seguinte exemplo da marca <script> anterior, o arquivo scripts.js está na pasta


wwwroot/js do aplicativo:

HTML

<script src="js/scripts.js"></script>

Quando o arquivo JS externo é fornecido por uma biblioteca de classes Razor,


especifique o arquivo JS usando o caminho de ativo da Web estático estável:
./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)} :

O segmento de linha para o diretório atual ( ./ ) é necessário para criar o caminho


de ativo estático correto para o arquivo JS.
O espaço reservado {PACKAGE ID} é a ID do pacote da biblioteca. A ID do pacote
terá como valor padrão o nome do assembly do projeto, se <PackageId> não for
especificado no arquivo de projeto.
O espaço reservado {SCRIPT PATH AND FILENAME (.js)} é o caminho e o nome do
arquivo em wwwroot .

HTML

<body>
...

<script src="_framework/blazor.{webassembly|server}.js"></script>
<script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}">
</script>
</body>

No seguinte exemplo da marca <script> anterior:

A biblioteca de classes Razor tem um nome de assembly ComponentLibrary e um


<PackageId> não é especificado no arquivo de projeto da biblioteca.
O arquivo scripts.js está na pasta wwwroot da biblioteca de classes.

HTML

<script src="./_content/ComponentLibrary/scripts.js"></script>
Para obter mais informações, consulte Consumir componentes Razor do ASP.NET Core
de uma RCL (biblioteca de classes) Razor.

Injetar um script após o início de Blazor


Carregue JS de um script injetado em wwwroot/index.html (Blazor WebAssembly) ou
Pages/_Layout.cshtml (Blazor Server) quando o aplicativo for inicializado:

Adicione autostart="false" à marca <script> que carrega o script Blazor.


Injete um script na marcação de elemento <head> que faz referência a um arquivo
JS personalizado depois de começar Blazor chamando Blazor.start().then(...) .
Coloque o script ( <script>...</script> ) dentro da marca </body> de fechamento
depois que o script Blazor for carregado.

O seguinte exemplo injeta o arquivo wwwroot/scripts.js após o início de Blazor:

HTML

<body>
...

<script src="_framework/blazor.{webassembly|server}.js"
autostart="false"></script>
<script>
Blazor.start().then(function () {
var customScript = document.createElement('script');
customScript.setAttribute('src', 'scripts.js');
document.head.appendChild(customScript);
});
</script>
</body>

O espaço reservado {webassembly|server} na marcação anterior é webassembly para um


aplicativo Blazor WebAssembly ( blazor.webassembly.js ) ou server para um aplicativo
Blazor Server ( blazor.server.js ).

Para obter mais informações sobre a inicialização de Blazor, veja Inicialização de Blazor
no ASP.NET Core.

Isolamento de JavaScript em módulos


JavaScript
Blazor habilita o isolamento de JavaScript (JS) em módulos JavaScript padrão
(especificação ECMAScript ).
O isolamento de JS oferece os seguintes benefícios:

O JS importado não polui mais o namespace global.


Os consumidores de uma biblioteca e componentes não precisam importar o JS
relacionado.

Para obter mais informações, consulte Chamar funções JavaScript de métodos .NET no
ASP.NET Core Blazor.

Arquivos JavaScript armazenados em cache


Arquivos JavaScript (JS) e outros ativos estáticos geralmente não são armazenados em
cache em clientes durante o desenvolvimento no ambiente Development. Durante o
desenvolvimento, as solicitações de ativo estático incluem o cabeçalho Cache-Control
com um valor de no-cache ou max-age com um valor zero ( 0 ).

Durante a produção no ambiente Production, os arquivos JS geralmente são


armazenados em cache pelos clientes.

Para desabilitar o cache do lado do cliente em navegadores, os desenvolvedores


geralmente adotam uma das seguintes abordagens:

Desabilitar o cache quando o console de ferramentas de desenvolvedor do


navegador estiver aberto. Diretrizes podem ser encontradas na documentação de
ferramentas de desenvolvedor de cada mantenedor do navegador:
Chrome DevTools
Ferramentas para Desenvolvedores do Firefox
Visão geral das Ferramentas para Desenvolvedores do Microsoft Edge
Execute uma atualização manual do navegador de qualquer página da Web do
aplicativo Blazor para recarregar arquivos JS do servidor. O Middleware de Cache
HTTP do ASP.NET Core sempre honra um cabeçalho Cache-Control no-cache
enviado por um cliente.

Para obter mais informações, consulte:

Ambientes Blazor do ASP.NET Core


Cache de resposta no ASP.NET Core
Chamar funções JavaScript de métodos
.NET no ASP.NET Core Blazor
Artigo • 10/01/2023 • 128 minutos para o fim da leitura

Este artigo explica como invocar funções JavaScript (JS) do .NET.

Para obter informações sobre como chamar métodos .NET do JS, consulte Chamar
métodos .NET de funções JavaScript no ASP.NET Core Blazor.

IJSRuntime é registrado pela Blazor estrutura . Para chamar do JS .NET, injete a


IJSRuntime abstração e chame um dos seguintes métodos:

IJSRuntime.InvokeAsync
JSRuntimeExtensions.InvokeAsync
JSRuntimeExtensions.InvokeVoidAsync

Para os métodos .NET anteriores que invocam JS funções:

O identificador de função ( String ) é relativo ao escopo global ( window ). Para


chamar window.someScope.someFunction , o identificador é someScope.someFunction .
Não é necessário registrar a função antes que ela seja chamada.
Passe qualquer número de JSargumentos serializáveis on para Object[] uma JS
função.
O token de cancelamento ( CancellationToken ) propaga uma notificação de que as
operações devem ser canceladas.
TimeSpan representa um limite de tempo para uma JS operação.

O TValue tipo de retorno também deve ser JSON serializável. TValue deve
corresponder ao tipo .NET que melhor mapeia para o JStipo ON retornado.
Um JS Promise é retornado para InvokeAsync métodos . InvokeAsync
desencapsular o Promise e retorna o valor aguardado pelo Promise .

Para Blazor aplicativos com pré-geração habilitada, a chamada JS para não é possível
durante a pré-geração. Para obter mais informações, consulte a seção Pré-geração .

O exemplo a seguir é baseado em TextDecoder , um JSdecodificador baseado em . O


exemplo demonstra como invocar uma JS função de um método C# que descarrega um
requisito do código do desenvolvedor para uma API existente JS . A JS função aceita
uma matriz de bytes de um método C#, decodifica a matriz e retorna o texto para o
componente para exibição.

HTML
<script>
window.convertArray = (win1251Array) => {
var win1251decoder = new TextDecoder('windows-1251');
var bytes = new Uint8Array(win1251Array);
var decodedArray = win1251decoder.decode(bytes);
console.log(decodedArray);
return decodedArray;
};
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

O seguinte CallJsExample1 componente:

Invoca a convertArray JS função com InvokeAsync ao selecionar um botão


( Convert Array ).
Depois que a JS função é chamada, a matriz passada é convertida em uma cadeia
de caracteres. A cadeia de caracteres é retornada ao componente para exibição
( text ).

Pages/CallJsExample1.razor :

razor

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
@text
</p>

<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on
IMDB</a>
</p>
@code {
private MarkupString text;

private uint[] quoteArray =


new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112,
32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85,
110,
105, 118, 101, 114, 115, 101, 10, 10,
};

private async Task ConvertArray()


{
text = new(await JS.InvokeAsync<string>("convertArray",
quoteArray));
}
}

API JavaScript restrita a gestos do usuário


Esta seção só se aplica a Blazor Server aplicativos.

Algumas APIs JavaScript (JS) do navegador só podem ser executadas no contexto de um


gesto do usuário, como usar a (documentação doFullscreen API MDN) . Essas APIs não
podem ser chamadas por meio do JS mecanismo de interoperabilidade em Blazor
Server aplicativos porque o tratamento de eventos da interface do usuário é executado
de forma assíncrona e geralmente não está mais no contexto do gesto do usuário. O
aplicativo deve manipular o evento de interface do usuário completamente em
JavaScript, portanto, use onclick em vez do atributo de Blazordiretiva de . @onclick

Invocar funções JavaScript sem ler um valor


retornado ( InvokeVoidAsync )
Use InvokeVoidAsync quando:

O .NET não é necessário para ler o resultado de uma chamada javaScript (JS).
JS as funções retornam void(0)/void 0 ou indefinidas .

Forneça uma displayTickerAlert1 JS função. A função é chamada com InvokeVoidAsync


e não retorna um valor:

HTML
<script>
window.displayTickerAlert1 = (symbol, price) => {
alert(`${symbol}: $${price}!`);
};
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

Exemplo de componente ( .razor ) ( InvokeVoidAsync )


TickerChanged chama o handleTickerChanged1 método no componente a seguir
CallJsExample2 .

Pages/CallJsExample2.razor :

razor

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
<button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)


{
<p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
private Random r = new();
private string? stockSymbol;
private decimal price;

private async Task SetStock()


{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
Exemplo de classe ( .cs ) ( InvokeVoidAsync )
JsInteropClasses1.cs :

C#

using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable


{
private readonly IJSRuntime js;

public JsInteropClasses1(IJSRuntime js)


{
this.js = js;
}

public async ValueTask TickerChanged(string symbol, decimal price)


{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}

public void Dispose()


{
}
}

TickerChanged chama o handleTickerChanged1 método no componente a seguir


CallJsExample3 .

Pages/CallJsExample3.razor :

razor

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
<button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)


{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;

protected override void OnInitialized()


{
jsClass = new(JS);
}

private async Task SetStock()


{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0,
26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}

public void Dispose() => jsClass?.Dispose();


}

Invocar funções JavaScript e ler um valor


retornado ( InvokeAsync )
Use InvokeAsync quando o .NET deve ler o resultado de uma chamada JavaScript (JS).

Forneça uma displayTickerAlert2 JS função. O exemplo a seguir retorna uma cadeia de


caracteres para exibição pelo chamador:

HTML

<script>
window.displayTickerAlert2 = (symbol, price) => {
if (price < 20) {
alert(`${symbol}: $${price}!`);
return "User alerted in the browser.";
} else {
return "User NOT alerted.";
}
};
</script>

7 Observação
Para obter diretrizes gerais sobre JS a localização e nossas recomendações para
aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

Exemplo de componente ( .razor ) ( InvokeAsync )


TickerChanged chama o handleTickerChanged2 método e exibe a cadeia de caracteres

retornada no componente a seguir CallJsExample4 .

Pages/CallJsExample4.razor :

razor

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
<button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)


{
<p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)


{
<p>@result</p>
}

@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private string? result;

private async Task SetStock()


{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol,
price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
Exemplo de classe ( .cs ) ( InvokeAsync )
JsInteropClasses2.cs :

C#

using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable


{
private readonly IJSRuntime js;

public JsInteropClasses2(IJSRuntime js)


{
this.js = js;
}

public async ValueTask<string> TickerChanged(string symbol, decimal


price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol,
price);
}

public void Dispose()


{
}
}

TickerChanged chama o handleTickerChanged2 método e exibe a cadeia de caracteres


retornada no componente a seguir CallJsExample5 .

Pages/CallJsExample5.razor :

razor

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
<button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)


{
<p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)


{
<p>@result</p>
}

@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;

protected override void OnInitialized()


{
jsClass = new(JS);
}

private async Task SetStock()


{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0,
26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol,
price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}

public void Dispose() => jsClass?.Dispose();


}

Cenários dinâmicos de geração de conteúdo


Para a geração dinâmica de conteúdo com BuildRenderTree, use o [Inject] atributo :

razor

[Inject]
IJSRuntime JS { get; set; }

Pré-geração
Esta seção se aplica a Blazor Server aplicativos hospedados Blazor WebAssembly e que
pré-geram Razor componentes. A pré-geração é abordada em Pré-geração e integração
ASP.NET Core Razor componentes.

Embora um aplicativo esteja pré-gerando, determinadas ações, como chamar em


JavaScript (JS), não são possíveis.

Para o exemplo a seguir, a setElementText1 função é colocada dentro do <head>


elemento . A função é chamada com JSRuntimeExtensions.InvokeVoidAsync e não
retorna um valor.

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

HTML

<script>
window.setElementText1 = (element, text) => element.innerText = text;
</script>

2 Aviso

O exemplo anterior modifica o DOM (Modelo de Objeto de Documento)


diretamente para fins de demonstração. A modificação direta do DOM com JS não
é recomendada na maioria dos cenários porque JS pode interferir no
Blazorcontrole de alterações. Para obter mais informações, consulte ASP.NET Core
Blazor interoperabilidade do JavaScript (JSinteroperabilidade).

O OnAfterRender{Async} evento de ciclo de vida não é chamado durante o processo de


pré-geração no servidor. Substitua o OnAfterRender{Async} método para atrasar JS
chamadas de interoperabilidade até que o componente seja renderizado e interativo no
cliente após a pré-geração.

Pages/PrerenderedInterop1.razor :

razor
@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<div @ref="divElement">Text during render</div>

@code {
private ElementReference divElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
await JS.InvokeVoidAsync(
"setElementText1", divElement, "Text after render");
}
}
}

7 Observação

O exemplo anterior polui o cliente com métodos globais. Para obter uma
abordagem melhor em aplicativos de produção, consulte Isolamento de JavaScript
em módulos JavaScript.

Exemplo:

JavaScript

export setElementText1 = (element, text) => element.innerText = text;

O componente a seguir demonstra como usar JS a interoperabilidade como parte da


lógica de inicialização de um componente de uma maneira compatível com a pré-
geração. O componente mostra que é possível disparar uma atualização de
renderização de dentro OnAfterRenderAsyncde . O desenvolvedor deve ter cuidado para
evitar a criação de um loop infinito nesse cenário.

Para o exemplo a seguir, a setElementText2 função é colocada dentro do <head>


elemento . A função é chamada com IJSRuntime.InvokeAsync e retorna um valor .

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

HTML

<script>
window.setElementText2 = (element, text) => {
element.innerText = text;
return text;
};
</script>

2 Aviso

O exemplo anterior modifica o DOM (Modelo de Objeto de Documento)


diretamente para fins de demonstração. A modificação direta do DOM com JS não
é recomendada na maioria dos cenários porque JS pode interferir no
Blazorcontrole de alterações. Para obter mais informações, consulte ASP.NET Core
Blazor interoperabilidade do JavaScript (JSinteroperabilidade).

Quando JSRuntime.InvokeAsync é chamado, o ElementReference é usado apenas em


OnAfterRenderAsync e não em nenhum método de ciclo de vida anterior porque não há
nenhum JS elemento até que o componente seja renderizado.

StateHasChangedé chamado para gerar novamente o componente com o novo estado


obtido da JS chamada de interoperabilidade (para obter mais informações, consulte
ASP.NET Core Razor renderização de componente). O código não cria um loop infinito
porque StateHasChanged só é chamado quando data é null .

Pages/PrerenderedInterop2.razor :

razor

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<p>
Get value via JS interop call:
<strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
Set value via JS interop call:
</p>
<div id="val-set-by-interop" @ref="divElement"></div>

@code {
private string? data;
private ElementReference divElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender && data == null)
{
data = await JS.InvokeAsync<string>(
"setElementText2", divElement, "Hello from interop call!");

StateHasChanged();
}
}
}

7 Observação

O exemplo anterior polui o cliente com métodos globais. Para obter uma
abordagem melhor em aplicativos de produção, consulte Isolamento de JavaScript
em módulos JavaScript.

Exemplo:

JavaScript

export setElementText2 = (element, text) => {


element.innerText = text;
return text;
};

Interoperabilidade JS síncrona em Blazor


WebAssembly aplicativos
Esta seção só se aplica a Blazor WebAssembly aplicativos.

As chamadas de interoperabilidade de JS são assíncronas por padrão,


independentemente do código chamado ser síncrono ou assíncrono. As chamadas são
assíncronas por padrão para garantir que os componentes sejam compatíveis entre os
modelos de hospedagem de Blazor, Blazor Server e Blazor WebAssembly. No Blazor
Server, todas as JS chamadas de interoperabilidade devem ser assíncronas porque são
enviadas por uma conexão de rede.
Se tiver certeza de que seu aplicativo só é executado no Blazor WebAssembly, você
pode optar por fazer chamadas de interoperabilidade síncronas JS . Isso tem um pouco
menos de sobrecarga do que fazer chamadas assíncronas e pode resultar em menos
ciclos de renderização porque não há estado intermediário enquanto aguarda
resultados.

Para fazer uma chamada síncrona do .NET para o JavaScript em um Blazor


WebAssembly aplicativo, converta IJSRuntime para IJSInProcessRuntime para fazer a JS
chamada de interoperabilidade:

razor

@inject IJSRuntime JS

...

@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>
("javascriptFunctionIdentifier");
}
}

Ao trabalhar com IJSObjectReference aplicativos ASP.NET Core 5.0 ou posterioresBlazor


WebAssembly, você pode usar IJSInProcessObjectReference de forma síncrona:

C#

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import",


"./scripts.js");

Local do JavaScript
Carregue o código JavaScript (JS) usando qualquer uma das abordagens descritas pelo
artigo de visão geral de interoperabilidade (interoperabilidade) do JavaScript (JS):

Carregar um script na marcação de <head> (geralmente, não recomendado)


Carregar um script na marcação de <body>
Carregar um script de um arquivo JavaScript externo (.js) agrupado com um
componente
Carregar um script de um arquivo JavaScript externo (.js)
Injetar um script após o início de Blazor

Para obter informações sobre como isolar scripts em JS módulos , consulte a seção
Isolamento de JavaScript em módulos JavaScript .

2 Aviso

Não coloque uma <script> marca em um arquivo de componente ( .razor )


porque a <script> marca não pode ser atualizada dinamicamente.

Isolamento de JavaScript em módulos


JavaScript
Blazor habilita o isolamento de JavaScript (JS) em módulos JavaScript padrão
(especificação ECMAScript ).

O isolamento de JS oferece os seguintes benefícios:

O JS importado não polui mais o namespace global.


Os consumidores de uma biblioteca e componentes não precisam importar o JS
relacionado.

Por exemplo, o módulo a seguir JS exporta uma JS função para mostrar um prompt de
janela do navegador . Coloque o código a seguir JS em um arquivo externo JS .

wwwroot/scripts.js :

JavaScript

export function showPrompt(message) {


return prompt(message, 'Type anything here');
}

Adicione o módulo anterior JS a um aplicativo ou biblioteca de classes como um ativo


Web estático na wwwroot pasta e importe o módulo para o código .NET chamando
InvokeAsync na IJSRuntime instância .

IJSRuntime importa o módulo como um IJSObjectReference, que representa uma


referência a um JS objeto do código .NET. Use o IJSObjectReference para invocar
funções exportadas JS do módulo.
Pages/CallJsExample6.razor :

razor

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
@result
</p>

@code {
private IJSObjectReference? module;
private string? result;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}

private async Task TriggerPrompt()


{
result = await Prompt("Provide some text");
}

public async ValueTask<string?> Prompt(string message) =>


module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (module is not null)
{
await module.DisposeAsync();
}
}
}

No exemplo anterior:
Por convenção, o import identificador é um identificador especial usado
especificamente para importar um JS módulo.
Especifique o arquivo externo JS do módulo usando seu caminho de ativo da Web
estático estável: ./{SCRIPT PATH AND FILENAME (.js)} , em que:
O segmento de linha para o diretório atual ( ./ ) é necessário para criar o
caminho de ativo estático correto para o arquivo JS.
O espaço reservado {SCRIPT PATH AND FILENAME (.js)} é o caminho e o nome
do arquivo em wwwroot .
Descarta o IJSObjectReference para coleta de lixo em
IAsyncDisposable.DisposeAsync.

A importação dinâmica de um módulo requer uma solicitação de rede, portanto, ela só


pode ser obtida de forma assíncrona chamando InvokeAsync.

IJSInProcessObjectReference representa uma referência a um JS objeto cujas funções

podem ser invocadas de forma síncrona em Blazor WebAssembly aplicativos. Para obter
mais informações, consulte a seção Interoperabilidade JS síncrona em Blazor
WebAssembly aplicativos .

7 Observação

Quando o arquivo externo JS for fornecido por uma Razor biblioteca de classes,
especifique o arquivo do JS módulo usando seu caminho de ativo web estático
estável: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)} :

O segmento de linha para o diretório atual ( ./ ) é necessário para criar o


caminho de ativo estático correto para o arquivo JS.
O espaço reservado {PACKAGE ID} é a ID do pacote da biblioteca. A ID do
pacote terá como valor padrão o nome do assembly do projeto, se
<PackageId> não for especificado no arquivo de projeto. No exemplo a seguir,

o nome do assembly da biblioteca é ComponentLibrary e o arquivo de projeto


da biblioteca não especifica <PackageId> .
O espaço reservado {SCRIPT PATH AND FILENAME (.js)} é o caminho e o nome
do arquivo em wwwroot . No exemplo a seguir, o arquivo externo JS
( script.js ) é colocado na pasta da biblioteca de wwwroot classes.

C#

var module = await js.InvokeAsync<IJSObjectReference>(


"import", "./_content/ComponentLibrary/scripts.js");
Para obter mais informações, consulte Consumir componentes Razor do ASP.NET
Core de uma RCL (biblioteca de classes) Razor.

Capturar referências a elementos


Alguns cenários de interoperabilidade do JavaScript (JS) exigem referências a elementos
HTML. Por exemplo, uma biblioteca de interface do usuário pode exigir uma referência
de elemento para inicialização ou talvez seja necessário chamar APIs semelhantes a
comandos em um elemento, como click ou play .

Capture referências a elementos HTML em um componente usando a seguinte


abordagem:

Adicione um @ref atributo ao elemento HTML.


Defina um campo do tipo ElementReference cujo nome corresponde ao valor do
@ref atributo.

O exemplo a seguir mostra a captura de uma referência ao username <input> elemento :

razor

<input @ref="username" ... />

@code {
private ElementReference username;
}

2 Aviso

Use apenas uma referência de elemento para modificar o conteúdo de um


elemento vazio que não interage com Blazor. Esse cenário é útil quando uma API
de terceiros fornece conteúdo para o elemento . Como Blazor não interage com o
elemento , não há nenhuma possibilidade de conflito entre Blazora representação
do elemento e o DOM (Document Object Model).

No exemplo a seguir, é perigoso alterar o conteúdo da lista não ordenada ( ul )


porque Blazor interage com o DOM para preencher os itens de lista desse
elemento ( <li> ) do Todos objeto :

razor

<ul @ref="MyList">
@foreach (var item in Todos)
{
<li>@item.Text</li>
}
</ul>

Se JS a interoperabilidade modificar o conteúdo do elemento MyList e Blazor


tentar aplicar diferenças ao elemento, as diferenças não corresponderão ao DOM.

Para obter mais informações, consulte ASP.NET Core Blazor interoperabilidade do


JavaScript (JSinteroperabilidade).

Um ElementReference é passado para o JS código por meio de JS interoperabilidade. O


JS código recebe uma HTMLElement instância , que pode ser usada com APIs DOM
normais. Por exemplo, o código a seguir define um método de extensão do .NET
( TriggerClickEvent ) que permite enviar um clique do mouse para um elemento .

A JS função clickElement cria um click evento no elemento HTML passado ( element ):

JavaScript

window.interopFunctions = {
clickElement : function (element) {
element.click();
}
}

Para chamar uma JS função que não retorna um valor, use


JSRuntimeExtensions.InvokeVoidAsync. O código a seguir dispara um evento do lado
click do cliente chamando a função anterior JS com o capturado ElementReference:

razor

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>

@code {
private ElementReference exampleButton;

public async Task TriggerClick()


{
await JS.InvokeVoidAsync(
"interopFunctions.clickElement", exampleButton);
}
}

Para usar um método de extensão, crie um método de extensão estático que recebe a
IJSRuntime instância :

C#

public static async Task TriggerClickEvent(this ElementReference elementRef,


IJSRuntime js)
{
await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

O clickElement método é chamado diretamente no objeto . O exemplo a seguir


pressupõe que o TriggerClickEvent método esteja disponível no JsInteropClasses
namespace :

razor

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>

@code {
private ElementReference exampleButton;

public async Task TriggerClick()


{
await exampleButton.TriggerClickEvent(JS);
}
}

) Importante

A exampleButton variável só é preenchida depois que o componente é renderizado.


Se um não preenchido ElementReference for passado para JS o código, o JS
código receberá um valor de null . Para manipular referências de elemento após a
renderização do componente, use os métodos de ciclo de vida
doOnAfterRenderAsync componente ou OnAfterRender.
Ao trabalhar com tipos genéricos e retornar um valor, use ValueTask<TResult>:

C#

public static ValueTask<T> GenericMethod<T>(this ElementReference


elementRef,
IJSRuntime js)
{
return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

O {JAVASCRIPT FUNCTION} espaço reservado é o identificador de JS função.

GenericMethod é chamado diretamente no objeto com um tipo . O exemplo a seguir

pressupõe que o GenericMethod está disponível no JsInteropClasses namespace :

razor

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
returnValue: @returnValue
</p>

@code {
private ElementReference username;
private string? returnValue;

private async Task OnClickMethod()


{
returnValue = await username.GenericMethod<string>(JS);
}
}

Elementos de referência entre componentes


Um ElementReference não pode ser passado entre componentes porque:

A instância só tem garantia de existir depois que o componente é renderizado, que


é durante ou após a execução do método de OnAfterRender/OnAfterRenderAsync
um componente.
Um ElementReference é um struct, que não pode ser passado como um parâmetro
de componente.

Para que um componente pai disponibilize uma referência de elemento para outros
componentes, o componente pai pode:

Permitir que componentes filho registrem retornos de chamada.


Invoque os retornos de chamada registrados durante o OnAfterRender evento
com a referência de elemento passada. Indiretamente, essa abordagem permite
que os componentes filho interajam com a referência de elemento do pai.

HTML

<style>
.red { color: red }
</style>

HTML

<script>
function setElementClass(element, className) {
var myElement = element;
myElement.classList.add(className);
}
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

Pages/CallJsExample7.razor (componente pai):

razor

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="@this" Title="How is Blazor working for you?" />


Pages/CallJsExample7.razor.cs :

C#

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages
{
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;

protected override void OnAfterRender(bool firstRender)


{
base.OnAfterRender(firstRender);

foreach (var subscription in subscriptions)


{
try
{
subscription.OnNext(title);
}
catch (Exception)
{
throw;
}
}
}

public void Dispose()


{
disposing = true;

foreach (var subscription in subscriptions)


{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}

subscriptions.Clear();
}

public IDisposable Subscribe(IObserver<ElementReference> observer)


{
if (disposing)
{
throw new InvalidOperationException("Parent being
disposed");
}

subscriptions.Add(observer);

return new Subscription(observer, this);


}

private class Subscription : IDisposable


{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}

public IObserver<ElementReference> Observer { get; }


public CallJsExample7 Self { get; }

public void Dispose()


{
Self.subscriptions.Remove(Observer);
}
}
}
}

No exemplo anterior, o namespace do aplicativo é BlazorSample com componentes na


Pages pasta . Se estiver testando o código localmente, atualize o namespace .

Shared/SurveyPrompt.razor (componente filho):

razor

<div class="alert alert-secondary mt-4">


<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>

<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark"
href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
</span>
and tell us what you think.
</div>

@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}

No exemplo anterior, o namespace do aplicativo é BlazorSample com componentes


compartilhados na Shared pasta . Se estiver testando o código localmente, atualize o
namespace .

Proteger chamadas de interoperabilidade do


JavaScript
Esta seção se aplica principalmente a Blazor Server aplicativos, mas Blazor WebAssembly
os aplicativos também podem definir JS tempos limite de interoperabilidade se as
condições garantirem isso.

Em Blazor Server aplicativos, a interoperabilidade do JavaScript (JS) pode falhar devido a


erros de rede e deve ser tratada como não confiável. Por padrão, Blazor Server os
aplicativos usam um tempo limite de um minuto para JS chamadas de
interoperabilidade. Se um aplicativo puder tolerar um tempo limite mais agressivo,
defina o tempo limite usando uma das abordagens a seguir.

Defina um tempo limite global no Program.cs com


CircuitOptions.JSInteropDefaultCallTimeout:

C#

builder.Services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

O {TIMEOUT} espaço reservado é um TimeSpan (por exemplo,


TimeSpan.FromSeconds(80) ).

Defina um tempo limite por invocação no código do componente. O tempo limite


especificado substitui o tempo limite global definido por JSInteropDefaultCallTimeout:

C#

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1"


});

No exemplo anterior:
O {TIMEOUT} espaço reservado é um TimeSpan (por exemplo,
TimeSpan.FromSeconds(80) ).
O {ID} espaço reservado é o identificador para a função a ser invocada. Por
exemplo, o valor someScope.someFunction invoca a função
window.someScope.someFunction .

Embora uma causa comum de falhas de JS interoperabilidade sejam falhas de rede em


Blazor Server aplicativos, tempos limite por invocação podem ser definidos para JS
chamadas de interoperabilidade em Blazor WebAssembly aplicativos. Embora não exista
nenhum SignalR circuito em um Blazor WebAssembly aplicativo, JS as chamadas de
interoperabilidade podem falhar por outros motivos que se aplicam aos Blazor
WebAssembly aplicativos.

Para obter mais informações sobre esgotamento de recursos, consulte Diretrizes de


mitigação de ameaças para ASP.NET Core Blazor Server.

Evitar referências de objeto circular


Objetos que contêm referências circulares não podem ser serializados no cliente para:

Chamadas de método .NET.


O método JavaScript chama de C# quando o tipo de retorno tem referências
circulares.

Bibliotecas JavaScript que renderizam a


interface do usuário
Às vezes, talvez você queira usar bibliotecas JavaScript (JS) que produzem elementos de
interface do usuário visíveis dentro do DOM (Modelo de Objeto de Documento) do
navegador. À primeira vista, isso pode parecer difícil porque Blazoro sistema diferido
depende de ter controle sobre a árvore de elementos DOM e se depara com erros se
algum código externo modificar a árvore DOM e invalidar seu mecanismo para aplicar
diferenças. Essa não é uma Blazorlimitação específica. O mesmo desafio ocorre com
qualquer estrutura de interface do usuário baseada em comparação.

Felizmente, é simples inserir a interface do usuário gerada externamente em uma Razor


interface do usuário do componente de forma confiável. A técnica recomendada é fazer
com que o código do componente ( .razor arquivo) produza um elemento vazio. No
que diz Blazorrespeito ao sistema de comparação, o elemento está sempre vazio,
portanto, o renderizador não se recursa no elemento e, em vez disso, deixa seu
conteúdo sozinho. Isso torna seguro preencher o elemento com conteúdo arbitrário
gerenciado externamente.

O exemplo a seguir demonstra o conceito . Dentro da instrução if quando firstRender


é true , interaja com unmanagedElement fora do uso JS de Blazor interoperabilidade. Por
exemplo, chame uma biblioteca externa JS para preencher o elemento. Blazor deixa o
conteúdo do elemento sozinho até que esse componente seja removido. Quando o
componente é removido, toda a subárvore DOM do componente também é removida.

razor

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
private ElementReference unmanagedElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
...
}
}
}

Considere o exemplo a seguir que renderiza um mapa interativo usando APIs mapbox
de software livre .

O módulo a seguir JS é colocado no aplicativo ou disponibilizado em uma Razor


biblioteca de classes.

7 Observação

Para criar o mapa do Mapbox , obtenha um token de acesso de Entrada do


Mapbox e forneça-o onde o {ACCESS TOKEN} aparece no código a seguir.

wwwroot/mapComponent.js :

JavaScript

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {


return new mapboxgl.Map({
container: element,
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40],
zoom: 9
});
}

export function setMapCenter(map, latitude, longitude) {


map.setCenter([longitude, latitude]);
}

Para produzir o estilo correto, adicione a seguinte marca de folha de estilos à página
HTML do host.

Adicione o seguinte <link> elemento à marcação de <head> elemento (local do


<head> conteúdo):

HTML

<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />

Pages/CallJsExample8.razor :

razor

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol,


UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo,
Japan</button>

@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}

private async Task ShowAsync(double latitude, double longitude)


{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude).AsTask();
}
}

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}

if (mapModule is not null)


{
await mapModule.DisposeAsync();
}
}
}

O exemplo anterior produz uma interface do usuário do mapa interativo. O usuário:

Pode arrastar para rolar ou aplicar zoom.


Selecione botões para ir para locais predefinidos.
No exemplo anterior:

O <div> com @ref="mapElement" é deixado vazio no que Blazor diz respeito. O


mapbox-gl.js script pode preencher com segurança o elemento e modificar seu
conteúdo. Use essa técnica com qualquer JS biblioteca que renderize a interface
do usuário. Você pode inserir componentes de uma estrutura SPA de JS terceiros
dentro Razor de componentes, desde que eles não tentem entrar em contato e
modificar outras partes da página. Não é seguro que o código externo JS
modifique elementos que Blazor não consideram vazios.
Ao usar essa abordagem, tenha em mente as regras sobre como Blazor retém ou
destrói elementos DOM. O componente lida com segurança com eventos de
clique de botão e atualiza a instância de mapa existente porque os elementos
DOM são mantidos sempre que possível por padrão. Se você estava renderizando
uma lista de elementos de mapa de dentro de um @foreach loop, você deseja usar
@key para garantir a preservação de instâncias de componente. Caso contrário, as

alterações nos dados da lista podem fazer com que as instâncias de componente
mantenham o estado das instâncias anteriores de maneira indesejável. Para obter
mais informações, consulte using @key to preserve elements and components.
O exemplo encapsula JS a lógica e as dependências em um módulo ES6 e carrega
o módulo dinamicamente usando o import identificador . Para obter mais
informações, consulte Isolamento de JavaScript em módulos JavaScript.

Suporte à matriz de bytes


Blazor dá suporte à interoperabilidade javaScript da matriz de bytes otimizada (JS) que
evita a codificação/decodificação de matrizes de bytes em Base64. O exemplo a seguir
usa JS a interoperabilidade para passar uma matriz de bytes para JavaScript.

Forneça uma receiveByteArray JS função. A função é chamada com InvokeVoidAsync e


não retorna um valor:

HTML

<script>
window.receiveByteArray = (bytes) => {
let utf8decoder = new TextDecoder();
let str = utf8decoder.decode(bytes);
return str;
};
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

Pages/CallJsExample9.razor :

razor

@page "/call-js-example-9"
@inject IJSRuntime JS

<h1>Call JS Example 9</h1>

<p>
<button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
@result
</p>

<p>
Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
private string? result;

private async Task SendByteArray()


{
var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68,
0x69,
0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79,
0x2c,
0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20,
0x4e,
0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e
};

result = await JS.InvokeAsync<string>("receiveByteArray", bytes);


}
}

Para obter informações sobre como usar uma matriz de bytes ao chamar o .NET do
JavaScript, consulte Chamar métodos .NET de funções JavaScript no ASP.NET Core
Blazor.

Limites de tamanho em chamadas de


interoperabilidade do JavaScript
Esta seção só se aplica a Blazor Server aplicativos. No Blazor WebAssembly, a estrutura
não impõe um limite ao tamanho das entradas e saídas de interoperabilidade do
JavaScript (JS).

No Blazor Server, JS as chamadas de interoperabilidade são limitadas em tamanho pelo


tamanho máximo de mensagem de entrada SignalR permitido para métodos de hub,
que é imposto por HubOptions.MaximumReceiveMessageSize (padrão: 32 KB). JS para
mensagens .NET SignalR maiores que MaximumReceiveMessageSize gerar um erro. A
estrutura não impõe um limite no tamanho de uma SignalR mensagem do hub para um
cliente.

Quando SignalR o registro em log não está definido como Depuração ou Rastreamento,
um erro de tamanho de mensagem aparece apenas no console de ferramentas para
desenvolvedores do navegador:

Erro: conexão desconectada com o erro 'Erro: o servidor retornou um erro ao fechar:
a conexão foi fechada com um erro.'.
Quando SignalR o log do lado do servidor é definido como Depuração ou
Rastreamento, o log do lado do servidor exibe um InvalidDataException para um erro de
tamanho de mensagem.

appsettings.Development.json :

JSON

{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}

Erro:

System.IO.InvalidDataException: o tamanho máximo da mensagem de 32768B foi


excedido. O tamanho da mensagem pode ser configurado em AddHubOptions.

Aumente o limite definindo MaximumReceiveMessageSize em Program.cs . O exemplo a


seguir define o tamanho máximo da mensagem de recebimento como 64 KB:

C#

builder.Services.AddServerSideBlazor()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 *
1024);

O aumento do SignalR limite de tamanho da mensagem de entrada tem o custo de


exigir mais recursos de servidor e expõe o servidor a riscos maiores de um usuário mal-
intencionado. Além disso, ler uma grande quantidade de conteúdo na memória como
cadeias de caracteres ou matrizes de bytes também pode resultar em alocações que
funcionam mal com o coletor de lixo, resultando em penalidades de desempenho
adicionais.

Considere as diretrizes a seguir ao desenvolver um código que transfere uma grande


quantidade de dados entre JS e Blazor em Blazor Server aplicativos:
Aproveite o suporte de interoperabilidade de streaming nativo para transferir
dados maiores que o limite de tamanho da SignalR mensagem de entrada:
Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor
Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor
Dicas gerais:
Não aloque objetos grandes no e no JS código C#.
Memória consumida livre quando o processo é concluído ou cancelado.
Imponha os seguintes requisitos adicionais para fins de segurança:
Declare o tamanho máximo do arquivo ou dos dados que podem ser
passados.
Declare a taxa mínima de upload do cliente para o servidor.
Depois que os dados são recebidos pelo servidor, os dados podem ser:
Armazenado temporariamente em um buffer de memória até que todos os
segmentos sejam coletados.
Consumido imediatamente. Por exemplo, os dados podem ser armazenados
imediatamente em um banco de dados ou gravados em disco à medida que
cada segmento é recebido.

Transmitir do .NET para o JavaScript


Blazor dá suporte a dados de streaming diretamente do .NET para JavaScript. Os fluxos
são criados usando um DotNetStreamReference.

DotNetStreamReference representa um fluxo .NET e usa os seguintes parâmetros:

stream : o fluxo enviado para JavaScript.

leaveOpen : determina se o fluxo é deixado aberto após a transmissão. Se um valor

não for fornecido, leaveOpen o padrão será false .

No JavaScript, use um buffer de matriz ou um fluxo legível para receber os dados:

Usando um ArrayBuffer :

JavaScript

async function streamToJavaScript(streamRef) {


const data = await streamRef.arrayBuffer();
}

Usando um ReadableStream :

JavaScript
async function streamToJavaScript(streamRef) {
const stream = await streamRef.stream();
}

No código C#:

C#

using var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen:


false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);

No exemplo anterior:

O {STREAM} espaço reservado representa o Stream enviado para JavaScript.


JS é uma instância injetada IJSRuntime .

Chamar métodos .NET de funções JavaScript em ASP.NET Core Blazor abrange a


operação inversa, transmitindo de JavaScript para .NET.

Blazor ASP.NET Core downloads de arquivo aborda como baixar um arquivo no Blazor.

Capturar exceções do JavaScript


Para capturar JS exceções, encapsule a JS interoperabilidade em um try-catch bloco e
capture um JSException.

No exemplo a seguir, a nonFunction JS função não existe. Quando a função não é


encontrada, o JSException fica preso com um Message que indica o seguinte erro:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor :

C#

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>

<p>
@errorMessage
</p>

@code {
private string? errorMessage;
private string? result;

private async Task CatchUndefinedJSFunction()


{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}

Anular uma função JavaScript de longa


execução
Use um JSAbortController com um CancellationTokenSource no componente para
anular uma função JavaScript de execução longa do código C#.

A classe a seguir JS Helpers contém uma função de execução longa simulada,


longRunningFn , para contar continuamente até que o AbortController.signal indique
que AbortController.abort foi chamado. A sleep função é para fins de demonstração
para simular a execução lenta da função de execução longa e não estaria presente no
código de produção. Quando um componente chama stopFn , o longRunningFn é
sinalizado para anular por meio da verificação condicional do while loop em
AbortSignal.aborted .

HTML

<script>
class Helpers {
static #controller = new AbortController();

static async #sleep(ms) {


return new Promise(resolve => setTimeout(resolve, ms));
}
static async longRunningFn() {
var i = 0;
while (!this.#controller.signal.aborted) {
i++;
console.log(`longRunningFn: ${i}`);
await this.#sleep(1000);
}
}

static stopFn() {
this.#controller.abort();
console.log('longRunningFn aborted!');
}
}

window.Helpers = Helpers;
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).

O seguinte CallJsExample12 componente:

Invoca a JS função longRunningFn quando o Start Task botão é selecionado. Um


CancellationTokenSource é usado para gerenciar a execução da função de
execução longa. CancellationToken.Register define um JS delegado de chamada de
interoperabilidade para executar a JS função stopFn quando o
CancellationTokenSource.Token é cancelado.
Quando o Cancel Task botão é selecionado, o CancellationTokenSource.Token é
cancelado com uma chamada para Cancel.
O CancellationTokenSource é descartado no Dispose método .

Pages/CallJsExample12.razor :

razor

@page "/call-js-example-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
private CancellationTokenSource? cts;

private async Task StartTask()


{
cts = new CancellationTokenSource();
cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

await JS.InvokeVoidAsync("Helpers.longRunningFn");
}

private void CancelTask()


{
cts?.Cancel();
}

public void Dispose()


{
cts?.Cancel();
cts?.Dispose();
}
}

O console de ferramentas de desenvolvedor de um navegador indica a execução da


função de JS execução prolongada após a seleção do Start Task botão e quando a
função é anulada após a seleção do Cancel Task botão:

Console

longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!

Interoperabilidade do JavaScript
[JSImport] / [JSExport]
Esta seção se aplica a Blazor WebAssembly aplicativos.

Como alternativa à interação com JavaScript () em aplicativos que usam Blazoro


IJSRuntime mecanismo de interoperabilidade com JS base na interface,
uma [JSImport] JS/ [JSExport] API de interoperabilidade está disponível para aplicativos
direcionados ao .NET 7 ou posterior.Blazor WebAssemblyJS
Para obter mais informações, consulte Interoperabilidade de Importação/JSExportação
do JavaScript JScom ASP.NET Core Blazor WebAssembly.

Interoperabilidade JavaScript não solicitada


Esta seção se aplica a Blazor WebAssembly aplicativos.

A interoperabilidade não solicitada usando a IJSUnmarshalledRuntime interface é


obsoleta e deve ser substituída pela interoperabilidade JavaScript/ [JSImport]
[JSExport] .

Para obter mais informações, consulte Interoperabilidade de Importação/JSExportação


do JavaScript JScom ASP.NET Core Blazor WebAssembly.

Tarefas de limpeza do DOM (Modelo de Objeto


de Documento) durante o descarte de
componentes
Não execute JS o código de interoperabilidade para tarefas de limpeza do DOM durante
o descarte de componentes. Em vez disso, use o MutationObserver padrão em
JavaScript no cliente pelos seguintes motivos:

O componente pode ter sido removido do DOM no momento em que o código de


limpeza for executado em Dispose{Async} .
Em um Blazor Server aplicativo, o Blazor renderizador pode ter sido descartado
pela estrutura no momento em que o código de limpeza é executado em
Dispose{Async} .

O MutationObserver padrão permite executar uma função quando um elemento é


removido do DOM.

Chamadas de interoperabilidade do JavaScript


sem um circuito
Esta seção só se aplica a Blazor Server aplicativos.

As chamadas de interoperabilidade do JavaScript (JS) não podem ser emitidas depois


que um SignalR circuito é desconectado. Sem um circuito durante o descarte de
componentes ou em qualquer outro momento em que um circuito não exista, o método
a seguir chama fail e registra uma mensagem informando que o circuito está
desconectado como um JSDisconnectedException:

JS chamadas de método de interoperabilidade


IJSRuntime.InvokeAsync
JSRuntimeExtensions.InvokeAsync
JSRuntimeExtensions.InvokeVoidAsync)
Dispose / DisposeAsync chama em qualquer IJSObjectReference.

Para evitar o registro em log JSDisconnectedException ou registrar informações


personalizadas em log, capture a exceção em uma try-catch instrução .

Para o seguinte exemplo de eliminação de componentes:

O componente implementa IAsyncDisposable.


objInstance é um IJSObjectReference.
JSDisconnectedException é capturado e não registrado.
Opcionalmente, você pode registrar informações personalizadas na catch
instrução em qualquer nível de log que preferir. O exemplo a seguir não registra
informações personalizadas porque pressupõe que o desenvolvedor não se
importa com quando ou onde os circuitos são desconectados durante o descarte
de componentes.

C#

async ValueTask IAsyncDisposable.DisposeAsync()


{
try
{
if (objInstance is not null)
{
await objInstance.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}

Se você precisar limpar seus próprios JS objetos ou executar outro JS código no cliente
depois que um circuito for perdido, use o MutationObserver padrão em JS no cliente.
O MutationObserver padrão permite executar uma função quando um elemento é
removido do DOM.

Para obter mais informações, consulte os seguintes artigos:


Lidar com erros em aplicativos ASP.NET CoreBlazor: a seção de interoperabilidade
do JavaScript discute o tratamento de erros em JS cenários de interoperabilidade.
Razor ASP.NET Core ciclo de vida do componente: a seção Descarte de
componentes com IDisposable e IAsyncDisposable descreve como implementar
padrões de descarte em Razor componentes.

Recursos adicionais
Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor
InteropComponent.razor exemplo (branch do repositório main GitHub
dotnet/AspNetCore) : o main branch representa o desenvolvimento atual da
unidade de produto para a próxima versão do ASP.NET Core. Para selecionar o
branch para uma versão diferente (por exemplo, release/5.0 ), use a lista suspensa
Alternar branches ou marcas para selecionar o branch.
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Lidar com erros em aplicativos ASP.NET Core Blazor (seção de interoperabilidade
JavaScript)
Chamar métodos .NET de funções
JavaScript no ASP.NET Core Blazor
Artigo • 10/01/2023 • 101 minutos para o fim da leitura

Este artigo explica como invocar métodos .NET do JavaScript (JS).

Para obter informações sobre como chamar JS funções do .NET, consulte Chamar
funções JavaScript de métodos .NET no ASP.NET Core Blazor.

Invocar um método .NET estático


Para invocar um método .NET estático do JavaScript (JS), use as JS funções :

DotNet.invokeMethodAsync (Recomendado): assíncrono para aplicativos Blazor


Server e Blazor WebAssembly .
DotNet.invokeMethod : síncrono somente para Blazor WebAssembly aplicativos.

Passe o nome do assembly que contém o método , o identificador do método .NET


estático e quaisquer argumentos.

No exemplo a seguir:

O {ASSEMBLY NAME} espaço reservado é o nome do assembly do aplicativo.


O {.NET METHOD ID} espaço reservado é o identificador do método .NET.
O {ARGUMENTS} espaço reservado são argumentos opcionais separados por vírgulas
para passar para o método , cada um dos quais deve ser JSserializável por ON.

JavaScript

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}',


{ARGUMENTS});

DotNet.invokeMethodAsync retorna um JS Promise que representa o resultado da


operação. DotNet.invokeMethod (Blazor WebAssembly somente) retorna o resultado da
operação.

) Importante

A função assíncrona ( invokeMethodAsync ) é preferencial em vez da versão síncrona


( invokeMethod ) para dar suporte Blazor Server a cenários.
O método .NET deve ser público, estático e ter o [JSInvokable] atributo .

No exemplo a seguir:

O {<T>} espaço reservado indica o tipo de retorno, que só é necessário para


métodos que retornam um valor.
O {.NET METHOD ID} espaço reservado é o identificador do método.

razor

@code {
[JSInvokable]
public static Task{<T>} {.NET METHOD ID}()
{
...
}
}

7 Observação

Não há suporte para a chamada de métodos genéricos abertos com métodos .NET
estáticos, mas há suporte com métodos de instância. Para obter mais informações,
consulte a seção Chamar métodos de classe genérica .NET .

No componente a seguir CallDotNetExample1 , o ReturnArrayAsync método C# retorna


uma int matriz. O [JSInvokable] atributo é aplicado ao método , o que torna o
métodovokable por JS.

Pages/CallDotNetExample1.razor :

razor

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
<button onclick="returnArrayAsync()">
Trigger .NET static method
</button>
</p>

@code {
[JSInvokable]
public static Task<int[]> ReturnArrayAsync()
{
return Task.FromResult(new int[] { 1, 2, 3 });
}
}

O <button> atributo HTML do onclick elemento é a atribuição do manipulador de


eventos do onclick JavaScript para processar click eventos, não Blazoro atributo de
diretiva do @onclick . A returnArrayAsync JS função é atribuída como o manipulador.

A função a seguir returnArrayAsync JS chama o ReturnArrayAsync método .NET do


componente anterior CallDotNetExample1 e registra o resultado no console de
ferramentas de desenvolvedor web do navegador. BlazorSample é o nome do assembly
do aplicativo.

HTML

<script>
window.returnArrayAsync = () => {
DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
.then(data => {
console.log(data);
});
};
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

Quando o Trigger .NET static method botão é selecionado, a saída do console de


ferramentas para desenvolvedores do navegador exibe os dados da matriz. O formato
da saída difere ligeiramente entre os navegadores. A saída a seguir mostra o formato
usado pelo Microsoft Edge:

Console

Array(3) [ 1, 2, 3 ]

Por padrão, o identificador do método .NET para a JS chamada é o nome do método


.NET, mas você pode especificar um identificador diferente usando o construtor de
[JSInvokable] atributo . No exemplo a seguir, DifferentMethodName é o identificador de
método atribuído para o ReturnArrayAsync método :

C#

[JSInvokable("DifferentMethodName")]

Na chamada para DotNet.invokeMethodAsync ou DotNet.invokeMethod (Blazor


WebAssembly somente), chame DifferentMethodName para executar o ReturnArrayAsync
método .NET:

DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');

DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (Blazor


WebAssembly somente)

7 Observação

O ReturnArrayAsync exemplo de método nesta seção retorna o resultado de um


Task sem o uso de palavras-chave explícitas em C# async e await . A codificação de
métodos com async e await é típica de métodos que usam a await palavra-chave
para retornar o valor de operações assíncronas.

ReturnArrayAsync método composto por async palavras-chave e await :

C#

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()
{
return await Task.FromResult(new int[] { 1, 2, 3 });
}

Para obter mais informações, consulte Programação assíncrona com async e await
no guia do C#.

Criar referências de dados e objetos JavaScript


para passar para o .NET
Chame DotNet.createJSObjectReference(jsObject) para construir uma JS referência de
objeto para que ela possa ser passada para o .NET, em jsObject que é o JS objeto
usado para criar a referência de JS objeto. O exemplo a seguir passa uma referência ao
objeto não serializável window para o .NET, que o ReceiveWindowObject recebe no
método C# como um IJSObjectReference:

JavaScript

DotNet.invokeMethodAsync('{APP NAMESPACE}', 'ReceiveWindowObject',


DotNet.createJSObjectReference(window));

C#

[JSInvokable]
public void ReceiveWindowObject(IJSObjectReference objRef)
{
...
}

No exemplo anterior, o {APP NAMESPACE} espaço reservado é o namespace do aplicativo.

Chame DotNet.createJSStreamReference(streamReference) para construir uma JS


referência de fluxo para que ela possa ser passada para o .NET, em streamReference que
é uma ArrayBuffer matriz , Blob ou qualquer matriz tipada , como Uint8Array ou
Float32Array , usada para criar a referência de JS fluxo.

Invocar um método .NET de instância


Para invocar um método .NET de instância do JavaScript (JS):

Passe a instância do .NET por referência para JS encapsulando a instância em um


DotNetObjectReference e chamando Create nela.
Invoque um método de instância do .NET usando JS invokeMethodAsync ou
invokeMethod (Blazor WebAssembly somente) do passado DotNetObjectReference.

A instância do .NET também pode ser passada como um argumento ao invocar


outros métodos .NET do JS.
Descarte o DotNetObjectReference.

As seções a seguir deste artigo demonstram várias abordagens para invocar um método
.NET de instância:

Passar um DotNetObjectReference para uma função JavaScript individual


Passar um DotNetObjectReference para uma classe com várias funções JavaScript
Chamar métodos de classe genérica do .NET
Exemplos de instância de classe
Classe auxiliar de método .NET da instância de componente
Passar um DotNetObjectReference para uma
função JavaScript individual
O exemplo nesta seção demonstra como passar um para uma DotNetObjectReference
função JavaScript (JS) individual.

A função a seguir sayHello1 JS recebe um DotNetObjectReference e chama


invokeMethodAsync para chamar o GetHelloMessage método .NET de um componente:

HTML

<script>
window.sayHello1 = (dotNetHelper) => {
return dotNetHelper.invokeMethodAsync('GetHelloMessage');
};
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

No exemplo anterior, o nome dotNetHelper da variável é arbitrário e pode ser alterado


para qualquer nome preferencial.

Para o seguinte CallDotNetExample2 componente:

O componente tem um JSmétodo .NET que pode ser faturado chamado


GetHelloMessage .

Quando o Trigger .NET instance method botão é selecionado, a JS função


sayHello1 é chamada com o DotNetObjectReference.
sayHello1 :

Chama GetHelloMessage e recebe o resultado da mensagem.


Retorna o resultado da mensagem para o método de chamada
TriggerDotNetInstanceMethod .

A mensagem retornada de sayHello1 em result é exibida para o usuário.


Para evitar uma perda de memória e permitir a coleta de lixo, a referência de
objeto .NET criada por DotNetObjectReference é descartada no Dispose método .

Pages/CallDotNetExample2.razor :
razor

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
<label>
Name: <input @bind="name" />
</label>
</p>

<p>
<button @onclick="TriggerDotNetInstanceMethod">
Trigger .NET instance method
</button>
</p>

<p>
@result
</p>

@code {
private string? name;
private string? result;
private DotNetObjectReference<CallDotNetExample2>? objRef;

protected override void OnInitialized()


{
objRef = DotNetObjectReference.Create(this);
}

public async Task TriggerDotNetInstanceMethod()


{
result = await JS.InvokeAsync<string>("sayHello1", objRef);
}

[JSInvokable]
public string GetHelloMessage() => $"Hello, {name}!";

public void Dispose()


{
objRef?.Dispose();
}
}

No exemplo anterior, o nome dotNetHelper da variável é arbitrário e pode ser alterado


para qualquer nome preferencial.

Para passar argumentos para um método de instância:


1. Adicione parâmetros à invocação do método .NET. No exemplo a seguir, um nome
é passado para o método . Adicione parâmetros adicionais à lista conforme
necessário.

HTML

<script>
window.sayHello2 = (dotNetHelper, name) => {
return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
};
</script>

No exemplo anterior, o nome dotNetHelper da variável é arbitrário e pode ser


alterado para qualquer nome preferencial.

2. Forneça a lista de parâmetros para o método .NET.

Pages/CallDotNetExample3.razor :

razor

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
<label>
Name: <input @bind="name" />
</label>
</p>

<p>
<button @onclick="TriggerDotNetInstanceMethod">
Trigger .NET instance method
</button>
</p>

<p>
@result
</p>

@code {
private string? name;
private string? result;
private DotNetObjectReference<CallDotNetExample3>? objRef;

protected override void OnInitialized()


{
objRef = DotNetObjectReference.Create(this);
}

public async Task TriggerDotNetInstanceMethod()


{
result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
}

[JSInvokable]
public string GetHelloMessage(string passedName) => $"Hello,
{passedName}!";

public void Dispose()


{
objRef?.Dispose();
}
}

No exemplo anterior, o nome dotNetHelper da variável é arbitrário e pode ser alterado


para qualquer nome preferencial.

Passar um DotNetObjectReference para uma


classe com várias funções JavaScript
O exemplo nesta seção demonstra como passar um para uma DotNetObjectReference
classe JavaScript (JS) com várias funções.

Crie e passe um DotNetObjectReference do OnAfterRenderAsync método de ciclo de


vida para uma JS classe para várias funções usarem. Verifique se o código .NET descarta
o DotNetObjectReference, como mostra o exemplo a seguir.

No componente a seguir CallDotNetExampleOneHelper , os Trigger JS function botões


chamam JS funções definindo a propriedade , nãoBlazor o JS onclick atributo de
diretiva. @onclick

Pages/CallDotNetExampleOneHelper.razor :

C#

@page "/call-dotnet-example-one-helper"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET Example</PageTitle>

<h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1>

<p>
<label>
Message: <input @bind="name" />
</label>
</p>

<p>
<button onclick="GreetingHelpers.sayHello()">
Trigger JS function <code>sayHello</code>
</button>
</p>

<p>
<button onclick="GreetingHelpers.welcomeVisitor()">
Trigger JS function <code>welcomeVisitor</code>
</button>
</p>

@code {
private string? name;
private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
dotNetHelper = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("GreetingHelpers.setDotNetHelper",
dotNetHelper);
}
}

[JSInvokable]
public string GetHelloMessage() => $"Hello, {name}!";

[JSInvokable]
public string GetWelcomeMessage() => $"Welcome, {name}!";

public void Dispose()


{
dotNetHelper?.Dispose();
}
}

No exemplo anterior:

JS é uma instância injetada IJSRuntime . IJSRuntime é registrado pela Blazor

estrutura .
O nome dotNetHelper da variável é arbitrário e pode ser alterado para qualquer
nome preferencial.
O componente deve descartar explicitamente o DotNetObjectReference para
permitir a coleta de lixo e evitar uma perda de memória.
HTML

<script>
class GreetingHelpers {
static dotNetHelper;

static setDotNetHelper(value) {
GreetingHelpers.dotNetHelper = value;
}

static async sayHello() {


const msg =
await
GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');
alert(`Message from .NET: "${msg}"`);
}

static async welcomeVisitor() {


const msg =
await
GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');
alert(`Message from .NET: "${msg}"`);
}
}

window.GreetingHelpers = GreetingHelpers;
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

No exemplo anterior:

A GreetingHelpers classe é adicionada ao window objeto para definir globalmente


a classe , que permite Blazor localizar a classe para JS interoperabilidade.
O nome dotNetHelper da variável é arbitrário e pode ser alterado para qualquer
nome preferencial.

Chamar métodos de classe genérica do .NET


As funções JavaScript (JS) podem chamar métodos de classe genérica .NET , em que
uma JS função chama um método .NET de uma classe genérica.

Na seguinte classe de tipo genérico ( GenericType<TValue> ):


A classe tem um único parâmetro de tipo ( TValue ) com uma única propriedade
genérica Value .
A classe tem dois métodos não genéricos marcados com o [JSInvokable] atributo ,
cada um com um parâmetro de tipo genérico chamado newValue :
Update atualiza de forma síncrona o valor de Value de newValue .

UpdateAsync atualiza de forma assíncrona o valor de depois de Value newValue

criar uma tarefa aguardável com Task.Yield que retorna de forma assíncrona
para o contexto atual quando aguardado.
Cada um dos métodos de classe grava o tipo de TValue e o valor de Value no
console. Gravar no console é apenas para fins de demonstração. Os aplicativos de
produção geralmente evitam gravar no console em favor do registro em log do
aplicativo. Para obter mais informações, consulte ASP.NET Core Blazor registro em
log e registro em log no .NET Core e ASP.NET Core.

7 Observação

Tipos e métodos genéricos abertos não especificam tipos para espaços reservados
de tipo. Por outro lado, genéricos fechados fornecem tipos para todos os espaços
reservados de tipo. Os exemplos nesta seção demonstram genéricos fechados, mas
há suporte para a invocação JS de métodos de instância de interoperabilidade com
genéricos abertos. Não há suporte para o uso de genéricos abertos para
invocações de método .NET estático, que foram descritas anteriormente neste
artigo.

Para obter mais informações, consulte os seguintes artigos:

Classes e métodos genéricos (documentação do C#)


Classes genéricas (Guia de Programação em C#)
Genéricos no .NET (documentação do .NET)

GenericType.cs :

C#

using Microsoft.JSInterop;

public class GenericType<TValue>


{
public TValue? Value { get; set; }

[JSInvokable]
public void Update(TValue newValue)
{
Value = newValue;
Console.WriteLine($"Update: GenericType<{typeof(TValue)}>:
{Value}");
}

[JSInvokable]
public async void UpdateAsync(TValue newValue)
{
await Task.Yield();
Value = newValue;

Console.WriteLine($"UpdateAsync: GenericType<{typeof(TValue)}>:
{Value}");
}
}

Na seguinte invokeMethodsAsync função:

Os métodos e UpdateAsync da classe de Update tipo genérico são chamados com


argumentos que representam cadeias de caracteres e números.
Blazor WebAssembly Os aplicativos dão suporte à chamada de métodos .NET de
forma síncrona com invokeMethod . syncInterop recebe um valor booliano que
indica se a JS interoperabilidade está ocorrendo em um Blazor WebAssembly
aplicativo. Quando syncInterop é true , invokeMethod é chamado com segurança.
Se o valor de syncInterop for false , somente a função assíncrona
invokeMethodAsync será chamada porque a JS interoperabilidade está em execução
em um Blazor Server aplicativo.
Para fins de demonstração, a DotNetObjectReference função chama ( invokeMethod
ou invokeMethodAsync ), o método .NET chamado ( Update ou UpdateAsync ) e o
argumento são gravados no console. Os argumentos usam um número aleatório
para permitir a correspondência da chamada de JS função com a invocação do
método .NET (também gravada no console no lado do .NET). O código de
produção geralmente não grava no console, seja no cliente ou no servidor. Os
aplicativos de produção geralmente dependem do registro em log do aplicativo.
Para obter mais informações, consulte ASP.NET Core Blazor registro em log e
registro em log no .NET Core e ASP.NET Core.

HTML

<script>
const randomInt = () => Math.floor(Math.random() * 99999);

window.invokeMethodsAsync = async (syncInterop, dotNetHelper1,


dotNetHelper2) => {
var n = randomInt();
console.log(`JS: invokeMethodAsync:Update('string ${n}')`);
await dotNetHelper1.invokeMethodAsync('Update', `string ${n}`);

n = randomInt();
console.log(`JS: invokeMethodAsync:UpdateAsync('string ${n}')`);
await dotNetHelper1.invokeMethodAsync('UpdateAsync', `string ${n}`);

if (syncInterop) {
n = randomInt();
console.log(`JS: invokeMethod:Update('string ${n}')`);
dotNetHelper1.invokeMethod('Update', `string ${n}`);
}

n = randomInt();
console.log(`JS: invokeMethodAsync:Update(${n})`);
await dotNetHelper2.invokeMethodAsync('Update', n);

n = randomInt();
console.log(`JS: invokeMethodAsync:UpdateAsync(${n})`);
await dotNetHelper2.invokeMethodAsync('UpdateAsync', n);

if (syncInterop) {
n = randomInt();
console.log(`JS: invokeMethod:Update(${n})`);
dotNetHelper2.invokeMethod('Update', n);
}
};
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

No seguinte componente GenericsExample :

A JS função invokeMethodsAsync é chamada quando o Invoke Interop botão é


selecionado.
Um par de DotNetObjectReference tipos é criado e passado para a JS função para
instâncias do GenericType como um string e um int .

Pages/GenericsExample.razor :

razor

@page "/generics-example"
@using System.Runtime.InteropServices
@inject IJSRuntime JS
@implements IDisposable
<p>
<button @onclick="InvokeInterop">Invoke Interop</button>
</p>

<ul>
<li>genericType1: @genericType1?.Value</li>
<li>genericType2: @genericType2?.Value</li>
</ul>

@code {
private GenericType<string> genericType1 = new() { Value = "string 0" };
private GenericType<int> genericType2 = new() { Value = 0 };
private DotNetObjectReference<GenericType<string>>? objRef1;
private DotNetObjectReference<GenericType<int>>? objRef2;

protected override void OnInitialized()


{
objRef1 = DotNetObjectReference.Create(genericType1);
objRef2 = DotNetObjectReference.Create(genericType2);
}

public async Task InvokeInterop()


{
var syncInterop =
RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));

await JS.InvokeVoidAsync(
"invokeMethodsAsync", syncInterop, objRef1, objRef2);
}

public void Dispose()


{
objRef1?.Dispose();
objRef2?.Dispose();
}
}

No exemplo anterior, JS é uma instância injetada IJSRuntime . IJSRuntime é registrado


pela Blazor estrutura .

O seguinte demonstra a saída típica do exemplo anterior quando o Invoke Interop


botão é selecionado em um Blazor WebAssembly aplicativo:

JS: invokeMethodAsync:Update('string 37802')


.NET: Update: GenericType<System.String>: string 37802
JS: invokeMethodAsync:UpdateAsync('string 53051')
JS: invokeMethod:Update('string 26784')
.NET: Update: GenericType<System.String>: string 26784
JS: invokeMethodAsync:Update(14107)
.NET: Atualização: GenericType<System.Int32>: 14107
JS: invokeMethodAsync:UpdateAsync(48995)
JS: invokeMethod:Update(12872)
.NET: Atualização: GenericType<System.Int32>: 12872
.NET: UpdateAsync: GenericType<System.String>: string 53051
.NET: UpdateAsync: GenericType<System.Int32>: 48995

Se o exemplo anterior for implementado em um Blazor Server aplicativo, as chamadas


síncronas com invokeMethod serão evitadas. A função assíncrona ( invokeMethodAsync ) é
preferencial em relação à versão síncrona ( invokeMethod ) em Blazor Server cenários.

Saída típica de um Blazor Server aplicativo:

JS: invokeMethodAsync:Update('string 34809')


.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync('string 93059')
JS: invokeMethodAsync:Update(41997)
.NET: Atualização: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

Os exemplos de saída anteriores demonstram que métodos assíncronos são executados


e concluídos em uma ordem arbitrária , dependendo de vários fatores, incluindo o
agendamento de thread e a velocidade da execução do método. Não é possível prever
de forma confiável a ordem de conclusão para chamadas de método assíncrono.

Exemplos de instância de classe


A seguinte sayHello1 JS função:

Chama o GetHelloMessage método .NET no passado DotNetObjectReference.


Retorna a mensagem de GetHelloMessage para o sayHello1 chamador.

HTML

<script>
window.sayHello1 = (dotNetHelper) => {
return dotNetHelper.invokeMethodAsync('GetHelloMessage');
};
</script>
7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JSinteroperabilidade).

No exemplo anterior, o nome dotNetHelper da variável é arbitrário e pode ser alterado


para qualquer nome preferencial.

A classe a seguir HelloHelper tem um JSmétodo .NETvokable chamado


GetHelloMessage . Quando HelloHelper é criado, o nome na Name propriedade é usado
para retornar uma mensagem de GetHelloMessage .

HelloHelper.cs :

C#

using Microsoft.JSInterop;

public class HelloHelper


{
public HelloHelper(string? name)
{
Name = name ?? "No Name";
}

public string? Name { get; set; }

[JSInvokable]
public string GetHelloMessage() => $"Hello, {Name}!";
}

O CallHelloHelperGetHelloMessage método na classe a seguir JsInteropClasses3 invoca


a JS função sayHello1 com uma nova instância de HelloHelper .

JsInteropClasses3.cs :

C#

using Microsoft.JSInterop;

public class JsInteropClasses3


{
private readonly IJSRuntime js;

public JsInteropClasses3(IJSRuntime js)


{
this.js = js;
}

public async ValueTask<string> CallHelloHelperGetHelloMessage(string?


name)
{
using var objRef = DotNetObjectReference.Create(new
HelloHelper(name));
return await js.InvokeAsync<string>("sayHello1", objRef);
}
}

Para evitar uma perda de memória e permitir a coleta de lixo, a referência de objeto
.NET criada por DotNetObjectReference é descartada quando a referência de objeto sai
do escopo com using var sintaxe.

Quando o Trigger .NET instance method botão é selecionado no componente a seguir


CallDotNetExample4 , JsInteropClasses3.CallHelloHelperGetHelloMessage é chamado

com o valor de name .

Pages/CallDotNetExample4.razor :

razor

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
<label>
Name: <input @bind="name" />
</label>
</p>

<p>
<button @onclick="TriggerDotNetInstanceMethod">
Trigger .NET instance method
</button>
</p>

<p>
@result
</p>

@code {
private string? name;
private string? result;
private JsInteropClasses3? jsInteropClasses;

protected override void OnInitialized()


{
jsInteropClasses = new JsInteropClasses3(JS);
}

private async Task TriggerDotNetInstanceMethod()


{
if (jsInteropClasses is not null)
{
result = await
jsInteropClasses.CallHelloHelperGetHelloMessage(name);
}
}
}

A imagem a seguir mostra o componente renderizado com o nome Amy Pond no Name
campo . Depois que o botão é selecionado, Hello, Amy Pond! é exibido na interface do
usuário:

O padrão anterior mostrado na JsInteropClasses3 classe também pode ser


implementado inteiramente em um componente.

Pages/CallDotNetExample5.razor :

razor

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
<label>
Name: <input @bind="name" />
</label>
</p>

<p>
<button @onclick="TriggerDotNetInstanceMethod">
Trigger .NET instance method
</button>
</p>

<p>
@result
</p>

@code {
private string? name;
private string? result;

public async Task TriggerDotNetInstanceMethod()


{
using var objRef = DotNetObjectReference.Create(new
HelloHelper(name));
result = await JS.InvokeAsync<string>("sayHello1", objRef);
}
}

Para evitar uma perda de memória e permitir a coleta de lixo, a referência de objeto
.NET criada por DotNetObjectReference é descartada quando a referência de objeto sai
do escopo com using var sintaxe.

A saída exibida pelo CallDotNetExample5 componente é Hello, Amy Pond! quando o


nome Amy Pond é fornecido no name campo .

No componente anterior CallDotNetExample5 , a referência de objeto .NET é descartada.


Se uma classe ou componente não descartar o , descarte-o DotNetObjectReferencedo
cliente chamando dispose no passado DotNetObjectReference:

JavaScript

window.{JS FUNCTION NAME} = (dotNetHelper) => {


dotNetHelper.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}');
dotNetHelper.dispose();
}

No exemplo anterior:

O {JS FUNCTION NAME} espaço reservado é o JS nome da função.


O nome dotNetHelper da variável é arbitrário e pode ser alterado para qualquer
nome preferencial.
O {ASSEMBLY NAME} espaço reservado é o nome do assembly do aplicativo.
O {.NET METHOD ID} espaço reservado é o identificador do método .NET.

Classe auxiliar de método .NET da instância de


componente
Uma classe auxiliar pode invocar um método de instância do .NET como um Action. As
classes auxiliares são úteis nos seguintes cenários:

Quando vários componentes do mesmo tipo são renderizados na mesma página.


Em Blazor Server aplicativos com vários usuários simultaneamente usando o
mesmo componente.

No exemplo a seguir:

O CallDotNetExample6 componente contém vários ListItem1 componentes, que é


um componente compartilhado na pasta do Shared aplicativo.
Cada ListItem1 componente é composto por uma mensagem e um botão.
Quando um ListItem1 botão de componente é selecionado, esse
ListItem1 método altera o texto do UpdateMessage item de lista e oculta o botão.

A classe a seguir MessageUpdateInvokeHelper mantém um JSmétodo .NET -invokable,


UpdateMessageCaller , para invocar o Action especificado quando a classe é instanciada.
BlazorSample é o nome do assembly do aplicativo.

MessageUpdateInvokeHelper.cs :

C#

using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper


{
private Action action;

public MessageUpdateInvokeHelper(Action action)


{
this.action = action;
}

[JSInvokable("BlazorSample")]
public void UpdateMessageCaller()
{
action.Invoke();
}
}
A função a seguir updateMessageCaller JS invoca o UpdateMessageCaller método .NET.
BlazorSample é o nome do assembly do aplicativo.

HTML

<script>
window.updateMessageCaller = (dotNetHelper) => {
dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
dotNetHelper.dispose();
}
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).

No exemplo anterior, o nome dotNetHelper da variável é arbitrário e pode ser alterado


para qualquer nome preferencial.

O componente a seguir ListItem1 é um componente compartilhado que pode ser


usado várias vezes em um componente pai e cria itens de lista ( <li>...</li> ) para uma
lista HTML ( <ul>...</ul> ou <ol>...</ol> ). Cada ListItem1 instância de componente
estabelece uma instância de MessageUpdateInvokeHelper com um Action definido como
seu UpdateMessage método.

Quando o botão de InteropCall um ListItem1 componente é selecionado,


updateMessageCaller é invocado com um criado DotNetObjectReference para a

MessageUpdateInvokeHelper instância. Isso permite que a estrutura chame


UpdateMessageCaller nessa ListItem1 MessageUpdateInvokeHelper instância. O passado

DotNetObjectReference é descartado em JS ( dotNetHelper.dispose() ).

Shared/ListItem1.razor :

razor

@inject IJSRuntime JS

<li>
@message
<button @onclick="InteropCall"
style="display:@display">InteropCall</button>
</li>
@code {
private string message = "Select one of these list item buttons.";
private string display = "inline-block";
private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

protected override void OnInitialized()


{
messageUpdateInvokeHelper = new
MessageUpdateInvokeHelper(UpdateMessage);
}

protected async Task InteropCall()


{
if (messageUpdateInvokeHelper is not null)
{
await JS.InvokeVoidAsync("updateMessageCaller",
DotNetObjectReference.Create(messageUpdateInvokeHelper));
}
}

private void UpdateMessage()


{
message = "UpdateMessage Called!";
display = "none";
StateHasChanged();
}
}

StateHasChanged é chamado para atualizar a interface do usuário quando message é


definido em UpdateMessage . Se StateHasChanged não for chamado, Blazor não terá como
saber que a interface do usuário deve ser atualizada quando o Action for invocado.

CallDotNetExample6 O componente pai a seguir inclui quatro itens de lista, cada um

uma instância do ListItem1 componente.

Pages/CallDotNetExample6.razor :

razor

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
<ListItem1 />
<ListItem1 />
<ListItem1 />
<ListItem1 />
</ul>
A imagem a seguir mostra o componente pai renderizado CallDotNetExample6 após a
seleção do segundo InteropCall botão:

O segundo ListItem1 componente exibiu a UpdateMessage Called! mensagem.


O InteropCall botão para o segundo ListItem1 componente não está visível
porque a propriedade CSS display do botão está definida none como .

Método .NET da instância do componente


chamado de DotNetObjectReference atribuído a
uma propriedade de elemento
A atribuição de um DotNetObjectReference a uma propriedade de um elemento HTML
permite chamar métodos .NET em uma instância de componente:

Uma referência de elemento é capturada (ElementReference).


No método do componente, uma função JavaScript (JS) é invocada com a
referência do elemento e a instância do OnAfterRender{Async}componente como
um DotNetObjectReference. A JS função anexa o DotNetObjectReference ao
elemento em uma propriedade .
Quando um evento de elemento é invocado em JS (por exemplo, onclick ), o
elemento anexado DotNetObjectReference é usado para chamar um método .NET.

Semelhante à abordagem descrita na seção classe auxiliar do método .NET da instância


do componente , essa abordagem é útil nos seguintes cenários:

Quando vários componentes do mesmo tipo são renderizados na mesma página.


Em Blazor Server aplicativos com vários usuários simultaneamente usando o
mesmo componente.
O método .NET é invocado de um JS evento (por exemplo, onclick ), não de um
Blazor evento (por exemplo, @onclick ).

No exemplo a seguir:

O CallDotNetExample7 componente contém vários ListItem2 componentes, que é


um componente compartilhado na pasta do Shared aplicativo.
Cada ListItem2 componente é composto por uma mensagem <span> de item de
lista e uma segunda <span> com uma display propriedade CSS definida inline-
block como para exibição.

Quando um ListItem2 item de lista de componentes é selecionado, esse


ListItem2 método altera o texto do UpdateMessage item de lista no primeiro

<span> e oculta o segundo <span> definindo sua display propriedade como

none .

A função a seguir assignDotNetHelper JS atribui o DotNetObjectReference a um


elemento em uma propriedade chamada dotNetHelper :

HTML

<script>
window.assignDotNetHelper = (element, dotNetHelper) => {
element.dotNetHelper = dotNetHelper;
}
</script>

A função a seguir interopCall JS usa o DotNetObjectReference para que o elemento


passado invoque um método .NET chamado UpdateMessage :

HTML

<script>
window.interopCall = async (element) => {
await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
}
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).
No exemplo anterior, o nome dotNetHelper da variável é arbitrário e pode ser alterado
para qualquer nome preferencial.

O componente a seguir ListItem2 é um componente compartilhado que pode ser


usado várias vezes em um componente pai e cria itens de lista ( <li>...</li> ) para uma
lista HTML ( <ul>...</ul> ou <ol>...</ol> ).

Cada ListItem2 instância de componente invoca a assignDotNetHelper JS função em


OnAfterRenderAsync com uma referência de elemento (o primeiro <span> elemento do
item de lista) e a instância do componente como um DotNetObjectReference.

Quando a mensagem de um ListItem2 componente é selecionada, interopCall é


invocado passando o <span> elemento como um parâmetro ( this ), que invoca o
UpdateMessage método . <span> NET. No UpdateMessage , StateHasChanged é chamado

para atualizar a interface do usuário quando message é definido e a display


propriedade do segundo <span> é atualizada. Se StateHasChanged não for chamado,
Blazor não terá como saber que a interface do usuário deve ser atualizada quando o
método é invocado.

O DotNetObjectReference é descartado quando o componente é descartado.

Shared/ListItem2.razor :

razor

@inject IJSRuntime JS

<li>
<span @ref="elementRef" onclick="interopCall(this)">@message</span>
<span style="display:@display">Not Updated Yet!</span>
</li>

@code {
private DotNetObjectReference<ListItem2>? objRef;
private ElementReference elementRef;
private string display = "inline-block";
private string message = "Select one of these list items.";

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
objRef = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("assignDotNetHelper", elementRef,
objRef);
}
}
[JSInvokable]
public void UpdateMessage()
{
message = "UpdateMessage Called!";
display = "none";
StateHasChanged();
}

public void Dispose() => objRef?.Dispose();


}

CallDotNetExample7 O componente pai a seguir inclui quatro itens de lista, cada um


uma instância do ListItem2 componente.

Pages/CallDotNetExample7.razor :

razor

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
<ListItem2 />
<ListItem2 />
<ListItem2 />
<ListItem2 />
</ul>

Interoperabilidade JS síncrona em Blazor


WebAssembly aplicativos
Esta seção só se aplica a Blazor WebAssembly aplicativos.

As chamadas de interoperabilidade de JS são assíncronas por padrão,


independentemente do código chamado ser síncrono ou assíncrono. As chamadas são
assíncronas por padrão para garantir que os componentes sejam compatíveis entre os
modelos de hospedagem de Blazor, Blazor Server e Blazor WebAssembly. No Blazor
Server, todas as JS chamadas de interoperabilidade devem ser assíncronas porque são
enviadas por uma conexão de rede.

Se tiver certeza de que seu aplicativo só é executado no Blazor WebAssembly, você


pode optar por fazer chamadas de interoperabilidade síncronas JS . Isso tem um pouco
menos sobrecarga do que fazer chamadas assíncronas e pode resultar em menos ciclos
de renderização porque não há estado intermediário enquanto aguarda resultados.
Para fazer uma chamada síncrona do JavaScript para o .NET em Blazor WebAssembly
aplicativos, use DotNet.invokeMethod em vez de DotNet.invokeMethodAsync .

Chamadas síncronas funcionarão se:

O aplicativo está em execução em Blazor WebAssembly, não Blazor Serverem .


A função chamada retorna um valor de forma síncrona. A função não é um async
método e não retorna um .NET Task ou JavaScript Promise .

Local do JavaScript
Carregue o código JavaScript (JS) usando qualquer uma das abordagens descritas pelo
artigo visão geral daJS interoperabilidade:

Carregar um script na marcação de <head> (geralmente, não recomendado)


Carregar um script na marcação de <body>
Carregar um script de um arquivo JavaScript externo (.js)
Injetar um script após o início de Blazor

Para obter informações sobre como isolar scripts em JS módulos , consulte a seção
Isolamento do JavaScript em módulos JavaScript .

2 Aviso

Não coloque uma <script> marca em um arquivo de componente ( .razor )


porque a <script> marca não pode ser atualizada dinamicamente.

Isolamento de JavaScript em módulos


JavaScript
Blazor habilita o isolamento de JavaScript (JS) em módulos JavaScript padrão
(especificação ECMAScript ).

O isolamento de JS oferece os seguintes benefícios:

O JS importado não polui mais o namespace global.


Os consumidores de uma biblioteca e componentes não precisam importar o JS
relacionado.

Para obter mais informações, consulte Chamar funções JavaScript de métodos .NET no
ASP.NET Core Blazor.
Evitar referências de objeto circular
Objetos que contêm referências circulares não podem ser serializados no cliente para:

Chamadas de método .NET.


O método JavaScript chama de C# quando o tipo de retorno tem referências
circulares.

Suporte à matriz de bytes


Blazor dá suporte à interoperabilidade javaScript da matriz de bytes otimizada (JS) que
evita codificar/decodificar matrizes de bytes em Base64. O exemplo a seguir usa JS a
interoperabilidade para passar uma matriz de bytes para o .NET.

Forneça uma sendByteArray JS função. A função é chamada por um botão no


componente e não retorna um valor:

HTML

<script>
window.sendByteArray = () => {
const data = new Uint8Array([0x45,0x76,0x65,0x72,0x79,0x74,0x68,0x69,
0x6e,0x67,0x27,0x73,0x20,0x73,0x68,0x69,0x6e,0x79,0x2c,
0x20,0x43,0x61,0x70,0x74,0x69,0x61,0x6e,0x2e,0x20,0x4e,
0x6f,0x74,0x20,0x74,0x6f,0x20,0x66,0x72,0x65,0x74,0x2e]);
DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
.then(str => {
alert(str);
});
};
</script>

7 Observação

Para obter diretrizes gerais sobre JS a localização e nossas recomendações para


aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).

Pages/CallDotNetExample8.razor :

razor

@page "/call-dotnet-example-8"
@using System.Text
<h1>Call .NET Example 8</h1>

<p>
<button onclick="sendByteArray()">Send Bytes</button>
</p>

<p>
Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
[JSInvokable]
public static Task<string> ReceiveByteArray(byte[] receivedBytes)
{
return Task.FromResult(
Encoding.UTF8.GetString(receivedBytes, 0,
receivedBytes.Length));
}
}

Para obter informações sobre como usar uma matriz de bytes ao chamar JavaScript do
.NET, consulte Chamar funções JavaScript de métodos .NET em ASP.NET Core Blazor.

Transmitir do JavaScript para o .NET


Blazor dá suporte ao streaming de dados diretamente do JavaScript para o .NET. Os
fluxos são solicitados usando a Microsoft.JSInterop.IJSStreamReference interface .

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync retorna um Stream e usa


os seguintes parâmetros:

maxAllowedSize : número máximo de bytes permitidos para a operação de leitura


do JavaScript, que usa como padrão 512.000 bytes, se não for especificado.
cancellationToken : um CancellationToken para cancelar a leitura.

Em JavaScript:

JavaScript

function streamToDotNet() {
return new Uint8Array(10000000);
}

No código C#:
C#

var dataReference =
await JS.InvokeAsync<IJSStreamReference>("streamToDotNet");
using var dataReferenceStream =
await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);

var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");


using var outputFileStream = File.OpenWrite(outputPath);
await dataReferenceStream.CopyToAsync(outputFileStream);

No exemplo anterior:

JS é uma instância injetada IJSRuntime . IJSRuntime é registrado pela Blazor

estrutura.
O dataReferenceStream é gravado em disco ( file.txt ) no caminho da pasta
temporária do usuário atual (GetTempPath).

Chamar funções JavaScript de métodos .NET em ASP.NET Core Blazor abrange a


operação inversa, transmitindo de .NET para JavaScript usando um
DotNetStreamReference.

Blazor ASP.NET Core uploads de arquivo aborda como carregar um arquivo no Blazor.

Limites de tamanho em chamadas de


interoperabilidade do JavaScript
Esta seção só se aplica a Blazor Server aplicativos. No Blazor WebAssembly, a estrutura
não impõe um limite ao tamanho das entradas e saídas de interoperabilidade do
JavaScript (JS).

No Blazor Server, JS as chamadas de interoperabilidade são limitadas em tamanho pelo


tamanho máximo de mensagem de entrada SignalR permitido para métodos de hub,
que é imposto por HubOptions.MaximumReceiveMessageSize (padrão: 32 KB). JS para
mensagens .NET SignalR maiores do que MaximumReceiveMessageSize gerar um erro.
A estrutura não impõe um limite no tamanho de uma SignalR mensagem do hub para
um cliente.

Quando SignalR o registro em log não está definido como Depuração ou Rastreamento,
um erro de tamanho de mensagem aparece apenas no console de ferramentas para
desenvolvedores do navegador:

Erro: conexão desconectada com o erro 'Erro: o servidor retornou um erro ao fechar:
a conexão foi fechada com um erro.'.
Quando SignalR o log do lado do servidor é definido como Depuração ou
Rastreamento, o log do lado do servidor exibe um InvalidDataException para um erro de
tamanho de mensagem.

appsettings.Development.json :

JSON

{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}

Erro:

System.IO.InvalidDataException: o tamanho máximo da mensagem de 32768B foi


excedido. O tamanho da mensagem pode ser configurado em AddHubOptions.

Aumente o limite definindo MaximumReceiveMessageSize em Program.cs . O exemplo a


seguir define o tamanho máximo da mensagem de recebimento como 64 KB:

C#

builder.Services.AddServerSideBlazor()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 *
1024);

O aumento do SignalR limite de tamanho da mensagem de entrada tem o custo de


exigir mais recursos de servidor e expõe o servidor a riscos maiores de um usuário mal-
intencionado. Além disso, ler uma grande quantidade de conteúdo na memória como
cadeias de caracteres ou matrizes de bytes também pode resultar em alocações que
funcionam mal com o coletor de lixo, resultando em penalidades de desempenho
adicionais.

Considere as diretrizes a seguir ao desenvolver um código que transfere uma grande


quantidade de dados entre JS e Blazor em Blazor Server aplicativos:
Aproveite o suporte de interoperabilidade de streaming nativo para transferir
dados maiores que o limite de tamanho da SignalR mensagem de entrada:
Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor
Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor
Dicas gerais:
Não aloque objetos grandes no e no JS código C#.
Memória consumida livre quando o processo é concluído ou cancelado.
Imponha os seguintes requisitos adicionais para fins de segurança:
Declare o tamanho máximo do arquivo ou dos dados que podem ser
passados.
Declare a taxa mínima de upload do cliente para o servidor.
Depois que os dados são recebidos pelo servidor, os dados podem ser:
Armazenado temporariamente em um buffer de memória até que todos os
segmentos sejam coletados.
Consumido imediatamente. Por exemplo, os dados podem ser armazenados
imediatamente em um banco de dados ou gravados em disco à medida que
cada segmento é recebido.

Interoperabilidade do JavaScript
[JSImport] / [JSExport]
Esta seção se aplica a Blazor WebAssembly aplicativos.

Como alternativa à interação com JavaScript () em aplicativos que usam Blazoro


IJSRuntime mecanismo de interoperabilidade com JS base na interface ,
uma [JSImport] JS/ [JSExport] API de interoperabilidade está disponível para aplicativos
direcionados ao .NET 7 ou posterior.Blazor WebAssemblyJS

Para obter mais informações, consulte Interoperabilidade de importação/JSexportação


de JavaScript JScom ASP.NET Core Blazor WebAssembly.

Tarefas de limpeza do DOM (Modelo de Objeto


de Documento) durante o descarte de
componentes
Não execute JS código de interoperabilidade para tarefas de limpeza do DOM durante o
descarte de componentes. Em vez disso, use o MutationObserver padrão em
JavaScript no cliente pelos seguintes motivos:
O componente pode ter sido removido do DOM no momento em que o código de
limpeza é executado no Dispose{Async} .
Em um Blazor Server aplicativo, o Blazor renderizador pode ter sido descartado
pela estrutura no momento em que o código de limpeza é executado em
Dispose{Async} .

O MutationObserver padrão permite executar uma função quando um elemento é


removido do DOM.

Chamadas de interoperabilidade do JavaScript


sem um circuito
Esta seção só se aplica a Blazor Server aplicativos.

As chamadas de interoperabilidade do JavaScript (JS) não podem ser emitidas depois


que um SignalR circuito é desconectado. Sem um circuito durante o descarte de
componentes ou em qualquer outro momento em que um circuito não exista, as
chamadas de método a seguir falham e registram uma mensagem informando que o
circuito está desconectado como um JSDisconnectedException:

JS chamadas de método de interoperabilidade


IJSRuntime.InvokeAsync
JSRuntimeExtensions.InvokeAsync
JSRuntimeExtensions.InvokeVoidAsync)
Dispose / DisposeAsync chama em qualquer IJSObjectReference.

Para evitar o registro em log JSDisconnectedException ou registrar informações


personalizadas, capture a exceção em uma try-catch instrução .

Para o seguinte exemplo de descarte de componentes:

O componente implementa IAsyncDisposable.


objInstance é um IJSObjectReference.

JSDisconnectedException é capturado e não registrado.


Opcionalmente, você pode registrar informações personalizadas na catch
instrução em qualquer nível de log que preferir. O exemplo a seguir não registra
informações personalizadas porque pressupõe que o desenvolvedor não se
importa com quando ou onde os circuitos são desconectados durante o descarte
de componentes.

C#
async ValueTask IAsyncDisposable.DisposeAsync()
{
try
{
if (objInstance is not null)
{
await objInstance.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}

Se você precisar limpar seus próprios JS objetos ou executar outro JS código no cliente
depois que um circuito for perdido, use o MutationObserver padrão em JS no cliente.
O MutationObserver padrão permite executar uma função quando um elemento é
removido do DOM.

Para obter mais informações, consulte os seguintes artigos:

Tratar erros em aplicativos ASP.NET CoreBlazor: a seção de interoperabilidade do


JavaScript discute o tratamento de erros em JS cenários de interoperabilidade.
Razor ASP.NET Core ciclo de vida do componente: a seção Descarte de
componentes com IDisposable e IAsyncDisposable descreve como implementar
padrões de descarte em Razor componentes.

Recursos adicionais
Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor
InteropComponent.razor exemplo (branch do repositório main GitHub
dotnet/AspNetCore) : o main branch representa o desenvolvimento atual da
unidade de produto para a próxima versão do ASP.NET Core. Para selecionar o
branch para uma versão diferente (por exemplo, release/5.0 ), use a lista suspensa
Alternar branches ou marcas para selecionar o branch.
Interação com o DOM (Modelo de Objeto do Documento)
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Tratar erros em aplicativos ASP.NET Core Blazor (seção de interoperabilidade do
JavaScript)
Chamar uma API Web de ASP.NET Core
Blazor
Artigo • 28/11/2022 • 65 minutos para o fim da leitura

Este artigo descreve como chamar uma API Web de um Blazor aplicativo.

7 Observação

Este artigo carregou Blazor Server a cobertura para chamar APIs Web. A Blazor
WebAssembly cobertura aborda os seguintes assuntos:

Blazor WebAssembly exemplos baseados em um aplicativo WebAssembly do


lado do cliente que chama uma API Web para criar, ler, atualizar e excluir itens
de lista de tarefas pendentes.
System.Net.Http.Json Pacote.

HttpClient configuração de serviço.

HttpClient auxiliares e JSON ( GetFromJsonAsync , PostAsJsonAsync ,

PutAsJsonAsync , DeleteAsync ).

IHttpClientFactory serviços e a configuração de um chamado HttpClient .

Digitou HttpClient .
HttpClient e HttpRequestMessage para personalizar solicitações.

Chame o exemplo de API Web com CORS (compartilhamento de recursos


entre origens) e como o CORS pertence aos Blazor WebAssembly aplicativos.
Como lidar com erros de resposta da API Web no código do desenvolvedor.
Blazor exemplos de componente de estrutura para testar o acesso à API Web.
Recursos adicionais para desenvolver Blazor WebAssembly aplicativos que
chamam uma API Web.

Blazor Server os aplicativos chamam APIs Web usando HttpClient instâncias,


normalmente criadas usando IHttpClientFactory. Para obter diretrizes que se aplicam a
Blazor Server, consulte Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET
Core.

Um Blazor Server aplicativo não inclui um HttpClient serviço por padrão. Forneça um
HttpClient para o aplicativo usando a infraestrutura deHttpClient fábrica.

Em Program.cs :
C#

builder.Services.AddHttpClient();

O componente a seguir Blazor ServerRazor faz uma solicitação para uma API Web para
branches do GitHub semelhante ao exemplo de Uso Básico no artigo Fazer solicitações
HTTP usando IHttpClientFactory no ASP.NET Core.

Pages/CallWebAPI.razor :

razor

@page "/call-web-api"
@using System.Text.Json
@using System.Text.Json.Serialization
@inject IHttpClientFactory ClientFactory

<h1>Call web API from a Blazor Server Razor component</h1>

@if (getBranchesError)
{
<p>Unable to get branches from GitHub. Please try again later.</p>
}
else
{
<ul>
@foreach (var branch in branches)
{
<li>@branch.Name</li>
}
</ul>
}

@code {
private IEnumerable<GitHubBranch> branches = Array.Empty<GitHubBranch>
();
private bool getBranchesError;
private bool shouldRender;

protected override bool ShouldRender() => shouldRender;

protected override async Task OnInitializedAsync()


{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

var client = ClientFactory.CreateClient();

var response = await client.SendAsync(request);


if (response.IsSuccessStatusCode)
{
using var responseStream = await
response.Content.ReadAsStreamAsync();
branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
getBranchesError = true;
}

shouldRender = true;
}

public class GitHubBranch


{
[JsonPropertyName("name")]
public string Name { get; set; }
}
}

Para obter um exemplo de trabalho adicional, consulte o Blazor Server exemplo de


upload de arquivo que carrega arquivos para um controlador de API Web no artigo
ASP.NET Core Blazor uploads de arquivo.

CORS (Compartilhamento de Recursos entre


Origens)
A segurança do navegador restringe uma página da Web de fazer solicitações para um
domínio diferente daquele que serviu a página da Web. Essa restrição é chamada de
política de mesma origem. A política de mesma origem restringe (mas não impede) que
um site mal-intencionado leia dados confidenciais de outro site. Para fazer solicitações
do navegador para um ponto de extremidade com uma origem diferente, o ponto de
extremidade deve habilitar o CORS (compartilhamento de recursos entre origens) .

Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no
ASP.NET Core.

Blazor exemplos de componente de estrutura


para testar o acesso à API Web
Várias ferramentas de rede estão disponíveis publicamente para testar aplicativos de
back-end da API Web diretamente, como o Firefox Browser Developer e o Postman .
Blazor A origem de referência da estrutura inclui HttpClient ativos de teste que são úteis
para teste:

HttpClientTest ativos no dotnet/aspnetcore repositório GitHub

Recursos adicionais
Blazor Server ASP.NET Core cenários de segurança adicionais: inclui cobertura
sobre como usar HttpClient para fazer solicitações de API Web seguras.
Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET Core
Impor HTTPS no ASP.NET Core
Habilitar o CORS (solicitações entre origens) no ASP.NET Core
Kestrel Configuração do ponto de extremidade HTTPS
CORS (compartilhamento de recursos entre origens) no W3C
Trabalhar com imagens no ASP.NET Core
Blazor
Artigo • 10/01/2023 • 8 minutos para o fim da leitura

Este artigo descreve cenários comuns para trabalhar com imagens em Blazor aplicativos.

Definir dinamicamente uma fonte de imagem


O exemplo a seguir demonstra como definir dinamicamente a origem de uma imagem
com um campo C#.

Para o exemplo nesta seção:

Obtenha três imagens de qualquer origem ou clique com o botão direito do


mouse em cada uma das imagens a seguir para salvá-las localmente. Nomeie as
imagens image1.png , image2.png e image3.png .

Coloque as imagens em uma nova pasta chamada images na raiz da Web do


aplicativo ( wwwroot ). O uso da images pasta é apenas para fins de demonstração.
Você pode organizar imagens em qualquer layout de pasta que preferir, incluindo
o fornecimento das imagens diretamente da wwwroot pasta.

No seguinte componente ShowImage1 :

A origem da imagem ( src ) é definida dinamicamente como o valor de


imageSource em C#.
O ShowImage método atualiza o imageSource campo com base em um argumento
de imagem id passado para o método .
Os botões renderizados chamam o ShowImage método com um argumento de
imagem para cada uma das três imagens disponíveis na images pasta. O nome do
arquivo é composto usando o argumento passado para o método e corresponde a
uma das três imagens na images pasta.

Pages/ShowImage1.razor :

razor
@page "/show-image-1"

<h1>Dynamic Image Source Example</h1>

@if (imageSource is not null)


{
<p>
<img src="@imageSource" />
</p>
}

@for (var i = 1; i <= 3; i++)


{
var imageId = i;
<button @onclick="() => ShowImage(imageId)">
Image @imageId
</button>
}

@code {
private string? imageSource;

private void ShowImage(int id)


{
imageSource = $"images/image{id}.png";
}
}

O exemplo anterior usa um campo C# para armazenar os dados de origem da imagem,


mas você também pode usar uma propriedade C# para armazenar os dados.

7 Observação

Não use uma variável de loop diretamente em uma expressão lambda, como i no
exemplo de loop anterior for . Caso contrário, a mesma variável é usada por todas
as expressões lambda, o que resulta no uso do mesmo valor em todas as lambdas.
Sempre capture o valor da variável em uma variável local e use a variável local. No
exemplo anterior:

A variável i de loop é atribuída a imageId .


imageId é usado na expressão lambda.

Como alternativa, use um foreach loop com Enumerable.Range, que não sofre do
problema anterior:

razor
@foreach (var imageId in Enumerable.Range(1,3))
{
<button @onclick="() => ShowImage(imageId)">
Image @imageId
</button>
}

Transmitir dados de imagem


Uma imagem pode ser enviada diretamente ao cliente usando Blazoros recursos de
interoperabilidade de streaming em vez de hospedar a imagem em uma URL pública.

O exemplo nesta seção transmite dados de origem da imagem usando a


interoperabilidade JavaScript (JS). A função a seguir setImage JS aceita a marca id e o
<img> fluxo de dados da imagem. A função executa as seguintes etapas:

Lê o fluxo fornecido em um ArrayBuffer .


Cria um Blob para encapsular o ArrayBuffer .
Cria uma URL de objeto para servir como o endereço para a imagem a ser
mostrada.
Atualizações o <img> elemento com o especificado imageElementId com a URL do
objeto que acabou de ser criada.
Para evitar vazamentos de memória, a função chama revokeObjectURL para
descartar a URL do objeto quando o componente terminar de trabalhar com uma
imagem.

HTML

<script>
window.setImage = async (imageElementId, imageStream) => {
const arrayBuffer = await imageStream.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const image = document.getElementById(imageElementId);
image.onload = () => {
URL.revokeObjectURL(url);
}
image.src = url;
}
</script>

7 Observação
Para obter diretrizes gerais sobre JS a localização e nossas recomendações para
aplicativos de produção, consulte ASP.NET Core Blazor interoperabilidade do
JavaScript (JS interoperabilidade).

O seguinte ShowImage2 componente:

Injeta serviços para um System.Net.Http.HttpClient e


Microsoft.JSInterop.IJSRuntime.
Inclui uma <img> marca para exibir uma imagem.
Tem um GetImageStreamAsync método C# para recuperar um Stream para uma
imagem. Um aplicativo de produção pode gerar dinamicamente uma imagem com
base no usuário específico ou recuperar uma imagem do armazenamento. O
exemplo a seguir recupera o avatar do .NET para o dotnet repositório GitHub.
Tem um SetImageAsync método que é disparado na seleção do botão pelo usuário.
SetImageAsync executa as seguintes etapas:

Recupera o Stream de GetImageStreamAsync .


Encapsula o Stream em um DotNetStreamReference, que permite transmitir os
dados da imagem para o cliente.
Invoca a setImage função JavaScript, que aceita os dados no cliente.

7 Observação

Blazor Server os aplicativos usam um serviço dedicado HttpClient para fazer


solicitações, portanto, nenhuma ação é necessária pelo desenvolvedor em Blazor
Server aplicativos para registrar um HttpClient serviço. Blazor WebAssembly os
aplicativos têm um registro de serviço padrão HttpClient quando o aplicativo é
criado a partir de um Blazor WebAssembly modelo de projeto. Se um HttpClient
registro de serviço não estiver presente em Program.cs um Blazor WebAssembly
aplicativo, forneça um adicionando builder.Services.AddHttpClient(); . Para saber
mais, confira Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET Core.

Pages/ShowImage2.razor :

razor

@page "/show-image-2"
@inject HttpClient Http
@inject IJSRuntime JS

<h1>Stream Image Data Example</h1>

<p>
<img id="image" />
</p>

<button @onclick="SetImageAsync">
Set Image
</button>

@code {
private async Task<Stream> GetImageStreamAsync()
{
return await Http.GetStreamAsync(
"https://avatars.githubusercontent.com/u/9141961");
}

private async Task SetImageAsync()


{
var imageStream = await GetImageStreamAsync();
var dotnetImageStream = new DotNetStreamReference(imageStream);
await JS.InvokeVoidAsync("setImage", "image", dotnetImageStream);
}
}

Recursos adicionais
Blazor carregamentos de arquivo ASP.NET Core
Blazor ASP.NET Core downloads de arquivo
Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor
Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Autenticação e autorização do Blazor no
ASP.NET Core
Artigo • 06/01/2023 • 60 minutos para o fim da leitura

Este artigo descreve o suporte do ASP.NET Core para a configuração e o gerenciamento


de segurança em aplicativos Blazor.

Os cenários de segurança são diferentes para aplicativos Blazor Server e Blazor


WebAssembly. Como os aplicativos Blazor Server do lado do servidor são executados no
servidor, as verificações de autorização podem determinar:

As opções de interface do usuário apresentadas ao usuário (por exemplo, as


entradas de menu disponíveis a um usuário).
As regras de acesso para áreas do aplicativo e componentes.

Aplicativos Blazor WebAssembly são executados no cliente. A autorização é somente


usada para determinar quais opções da interface do usuário serão apresentadas. Uma
vez que as verificações do lado do cliente podem ser modificadas ou ignoradas pelo
usuário, um aplicativo Blazor WebAssembly não pode aplicar regras de autorização de
acesso.

As convenções de autorização do Razor Pages não se aplicam a componentes Razor


roteáveis. Se um componente Razor não roteável for inserido em uma página, as
convenções de autorização da página afetarão indiretamente o componente Razor
junto com o restante do conteúdo da página.

Identity ASP.NET Core foi projetado para funcionar no contexto da comunicação de


solicitação e resposta HTTP, que geralmente não é o modelo de comunicação cliente-
servidor do Blazor aplicativo. Os aplicativos ASP.NET Core que usam o ASP.NET Core
Identity para gerenciamento de usuários devem usar Razor Pages em vez de
componentes Razor para interface do usuário relacionada a Identity, como registro de
usuário, logon, logoff e outras tarefas de gerenciamento de usuários.

Abstrações do ASP.NET Core, como SignInManager<TUser> eUserManager<TUser>,


não têm suporte em componentes Razor. Para obter mais informações sobre como usar
o ASP.NET Core Identity com Blazor, consulte Scaffold do ASP.NET Core Identity em um
aplicativo Blazor Server.

Autenticação
O Blazor usa os mecanismos de autenticação do ASP.NET Core existentes para
estabelecer a identidade do usuário. O mecanismo exato depende de como o aplicativo
Blazor está hospedado, Blazor WebAssembly ou Blazor Server.

autenticação Blazor WebAssembly


Em aplicativos Blazor WebAssembly, as verificações de autenticação podem ser
ignoradas porque todos os códigos do lado do cliente podem ser modificados pelos
usuários. Isso também ocorre com todas as tecnologias de aplicativo do lado do cliente,
incluindo estruturas de SPA do JavaScript ou aplicativos nativos em qualquer sistema
operacional.

Adicione o seguinte:

Uma referência de pacote para


Microsoft.AspNetCore.Components.Authorization .

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET,


consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de
consumo de pacotes (documentação do NuGet). Confirme as versões
corretas de pacote em NuGet.org .

O namespace Microsoft.AspNetCore.Components.Authorization para o arquivo do


aplicativo _Imports.razor .

Para lidar com a autenticação, o uso de um serviço AuthenticationStateProvider interno


ou personalizado é abordado nas seções a seguir.

Para obter mais informações sobre como criar aplicativos e a configuração, consulte
Proteger o ASP.NET Core Blazor WebAssembly.

autenticação Blazor Server


Os aplicativos Blazor Server operam em uma conexão em tempo real criada usando o
SignalR. A autenticação em aplicativos baseados em SignalR é realizada quando a
conexão é estabelecida. A autenticação pode ser baseada em um cookie ou um algum
outro token de portador.

O serviço AuthenticationStateProvider interno para aplicativos Blazor Server obtém


dados de estado de autenticação do HttpContext.User do ASP.NET Core. Essa é a
maneira que o estado de autenticação se integra a mecanismos de autenticação
existentes do ASP.NET Core.

) Importante

Não use IHttpContextAccessor em Razor componentes de Blazor Server


aplicativos. Blazoros aplicativos são executados fora do contexto do pipeline de
ASP.NET Core. Não HttpContext há garantia de que o IHttpContextAccessoresteja
disponível no e HttpContext não tem garantia de manter o contexto que iniciou o
Blazor aplicativo. Para obter mais informações, consulte Implicações de segurança
do uso IHttpContextAccessor no Blazor Server (dotnet/aspnetcore #45699) .
Para obter mais informações sobre como manter o estado do usuário em Blazor
Server aplicativos, consulte ASP.NET Core Blazor gerenciamento de estado.

Para obter mais informações sobre como criar aplicativos e a configuração, consulte
Proteger aplicativos Blazor Server do ASP.NET Core.

Serviço AuthenticationStateProvider
O AuthenticationStateProvider é o serviço subjacente usado pelos componentes
AuthorizeView e CascadingAuthenticationState para obter o estado de autenticação.

Normalmente, você não usa o AuthenticationStateProvider diretamente. Use as


abordagens do componente AuthorizeView ou Task<AuthenticationState> descritas
mais adiante neste artigo. A principal desvantagem de usar o
AuthenticationStateProvider diretamente é que o componente não será notificado
automaticamente se os dados subjacentes do estado de autenticação forem alterados.

O serviço AuthenticationStateProvider pode fornecer os dados de ClaimsPrincipal do


usuário atual, conforme mostrado no seguinte exemplo:

razor

@page "/"
@using System.Security.Claims
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider

<h3>ClaimsPrincipal Data</h3>

<button @onclick="GetClaimsPrincipalData">Get ClaimsPrincipal Data</button>

<p>@authMessage</p>
@if (claims.Count() > 0)
{
<ul>
@foreach (var claim in claims)
{
<li>@claim.Type: @claim.Value</li>
}
</ul>
}

<p>@surnameMessage</p>

@code {
private string authMessage;
private string surnameMessage;
private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

private async Task GetClaimsPrincipalData()


{
var authState = await
AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;

if (user.Identity.IsAuthenticated)
{
authMessage = $"{user.Identity.Name} is authenticated.";
claims = user.Claims;
surnameMessage =
$"Surname: {user.FindFirst(c => c.Type ==
ClaimTypes.Surname)?.Value}";
}
else
{
authMessage = "The user is NOT authenticated.";
}
}
}

Se user.Identity.IsAuthenticated for true e como o usuário é um ClaimsPrincipal, será


possível enumerar as declarações e avaliar a associação nas funções.

Para obter mais informações sobre DI (injeção de dependência) e serviços, consulte


Injeção de dependência de Blazor do ASP.NET Core e Injeção de dependência no
ASP.NET Core. Para obter informações sobre como implementar um
AuthenticationStateProvider personalizado em aplicativos Blazor Server, consulte
Proteger aplicativos Blazor Server do ASP.NET Core.

Expor o estado de autenticação como um


parâmetro em cascata
Se os dados do estado de autenticação forem necessários para a lógica do
procedimento, como ao realizar uma ação disparada pelo usuário, obtenha os dados de
estado de autenticação definindo um parâmetro em cascata do tipo
Task< AuthenticationState > :

razor

@page "/"

<button @onclick="LogUsername">Log username</button>

<p>@authMessage</p>

@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }

private string authMessage;

private async Task LogUsername()


{
var authState = await authenticationStateTask;
var user = authState.User;

if (user.Identity.IsAuthenticated)
{
authMessage = $"{user.Identity.Name} is authenticated.";
}
else
{
authMessage = "The user is NOT authenticated.";
}
}
}

Se user.Identity.IsAuthenticated for true , será possível enumerar as declarações e


avaliar a associação nas funções.

Configurar o parâmetro em cascata Task< AuthenticationState > usando os


componentes AuthorizeRouteView e CascadingAuthenticationState no componente App
( App.razor ):

razor

<CascadingAuthenticationState>
<Router ...>
<Found ...>
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView ...>
...
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

Em um aplicativo Blazor WebAssembly, adicione serviços para opções e autorização


para Program.cs :

C#

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

Em um aplicativo Blazor Server, os serviços de opções e autorização já estão presentes,


portanto, nenhuma ação adicional é necessária.

Autorização
Depois que o usuário é autenticado, as regras de autorização são aplicadas para
controlar o que ele poderá fazer.

O acesso geralmente é concedido ou negado com base nos seguintes casos:

Se o usuário está autenticado (conectado).


Se o usuário está em uma função.
Se o usuário tem uma declaração.
Se uma política é atendida.

Todos esses conceitos são iguais no MVC do ASP.NET Core ou em aplicativos Razor
Pages. Para obter mais informações sobre a segurança do ASP.NET Core, confira os
artigos em Segurança e Identity do ASP.NET Core.

Componente AuthorizeView
O componente AuthorizeView exibe de modo seletivo o conteúdo da interface do
usuário, caso o usuário esteja autorizado. Essa abordagem é útil quando você precisa
apenas exibir dados para o usuário e não precisa usar a identidade dele na lógica de
procedimento.
O componente expõe uma variável context do tipo AuthenticationState, que pode ser
usada para acessar informações sobre o usuário conectado:

razor

<AuthorizeView>
<h1>Hello, @context.User.Identity.Name!</h1>
<p>You can only see this content if you're authenticated.</p>
</AuthorizeView>

Também é possível fornecer um conteúdo diferente para ser exibido caso o usuário não
esteja autorizado:

razor

<AuthorizeView>
<Authorized>
<h1>Hello, @context.User.Identity.Name!</h1>
<p>You can only see this content if you're authorized.</p>
<button @onclick="SecureMethod">Authorized Only Button</button>
</Authorized>
<NotAuthorized>
<h1>Authentication Failure!</h1>
<p>You're not signed in.</p>
</NotAuthorized>
</AuthorizeView>

@code {
private void SecureMethod() { ... }
}

O conteúdo das marcas <Authorized> e <NotAuthorized> pode incluir itens arbitrários,


como outros componentes interativos.

Um manipulador de eventos padrão para um elemento autorizado, como o método


SecureMethod para o elemento <button> no exemplo anterior, só pode ser invocado por
um usuário autorizado.

As condições de autorização, como funções ou políticas que controlam o acesso ou as


opções da interface do usuário, são abordadas na seção Autorização.

Se as condições de autorização não forem especificadas, o AuthorizeView usará uma


política padrão e tratará:

Usuários autenticados (conectados) como autorizados.


Usuários não autenticados (não conectados) como não autorizados.
O componente AuthorizeView pode ser usado no componente NavMenu
( Shared/NavMenu.razor ) para exibir um componente NavLink (NavLink), mas observe que
essa abordagem remove apenas o item de lista da saída renderizada. Isso não impede
que o usuário navegue até o componente.

Aplicativos criados com base em um modelo de projeto Blazor que incluem a


autenticação usam um componente LoginDisplay que depende de um componente
AuthorizeView . O componente AuthorizeView exibe seletivamente o conteúdo aos
usuários para trabalho relacionado ao Identity. O exemplo a seguir é do modelo de
projeto Blazor WebAssembly.

Shared/LoginDisplay.razor :

razor

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation

<AuthorizeView>
<Authorized>
Hello, @context.User.Identity?.Name!
<button class="nav-link btn btn-link" @onclick="BeginLogOut">Log
out</button>
</Authorized>
<NotAuthorized>
<a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>

@code{
public void BeginLogOut()
{
Navigation.NavigateToLogout("authentication/logout");
}
}

O exemplo a seguir é do modelo de projeto Blazor Server e usa pontos de extremidade


Identity do ASP.NET Core na área Identity do aplicativo para processar o trabalho
relacionado ao Identity.

Shared/LoginDisplay.razor :

razor

<AuthorizeView>
<Authorized>
<a href="Identity/Account/Manage">Hello,
@context.User.Identity.Name!</a>
<form method="post" action="Identity/Account/LogOut">
<button type="submit" class="nav-link btn btn-link">Log
out</button>
</form>
</Authorized>
<NotAuthorized>
<a href="Identity/Account/Register">Register</a>
<a href="Identity/Account/Login">Log in</a>
</NotAuthorized>
</AuthorizeView>

Autorização baseada em funções e em políticas


O componente AuthorizeView dá suporte à autorização baseada em funções ou baseada
em políticas.

Para autorização baseada em função, use o Roles parâmetro . No exemplo a seguir, o


usuário deve ter uma declaração de função para (ou ambas) as Admin funções ou
Superuser :

razor

<AuthorizeView Roles="Admin, Superuser">


<p>You can only see this if you're an Admin or Superuser.</p>
</AuthorizeView>

Para obter mais informações, incluindo diretrizes de configuração, consulte Autorização


baseada em função no ASP.NET Core.

Para a autorização baseada em políticas, use o parâmetro Policy:

razor

<AuthorizeView Policy="ContentEditor">
<p>You can only see this if you satisfy the "ContentEditor" policy.</p>
</AuthorizeView>

A autorização baseada em declarações é um caso especial de autorização baseada em


políticas. Por exemplo, você pode definir uma política que exige que os usuários tenham
determinada declaração. Para obter mais informações, consulte Autorização baseada em
política no ASP.NET Core.

Essas APIs podem ser usadas em aplicativos Blazor Server ou Blazor WebAssembly.

Se Roles e Policy não forem especificados, o AuthorizeView usará a política padrão.


Como as comparações de cadeia de caracteres do .NET diferenciam maiúsculas de
minúsculas por padrão, a correspondência de nomes de função e política também
diferencia maiúsculas de minúsculas. Por exemplo, Admin (maiúsculas A ) não é tratado
como a mesma função admin que (minúscula ). a

O caso Pascal normalmente é usado para nomes de função e política (por exemplo,
BillingAdministrator ), mas o uso do caso Pascal não é um requisito estrito. Diferentes

esquemas de uso de maiúsculas e minúsculas, como caixa de camelo, estojo de kebab e


estojo de cobra, são permitidos. O uso de espaços em nomes de função e política
também é incomum, mas permitido. Por exemplo, é um formato de função ou nome de
política incomum em aplicativos .NET, billing administrator mas válido.

Conteúdo exibido durante a autenticação assíncrona


O Blazor permite que o estado de autenticação seja determinado de modo assíncrono.
O cenário principal dessa abordagem ocorre em aplicativos Blazor WebAssembly que
fazem uma solicitação a um ponto de extremidade externo para autenticação.

Enquanto a autenticação estiver em andamento, AuthorizeView não exibirá nenhum


conteúdo por padrão. Para exibir o conteúdo enquanto a autenticação ocorre, use a
marca <Authorizing> :

razor

<AuthorizeView>
<Authorized>
<h1>Hello, @context.User.Identity.Name!</h1>
<p>You can only see this content if you're authenticated.</p>
</Authorized>
<Authorizing>
<h1>Authentication in progress</h1>
<p>You can only see this content while authentication is in
progress.</p>
</Authorizing>
</AuthorizeView>

Normalmente, essa abordagem não é aplicável a aplicativos Blazor Server. Os aplicativos


Blazor Server ficam conhecendo o estado de autenticação assim que ele é estabelecido.
O conteúdo Authorizing pode ser fornecido em um componente Blazor Server do
aplicativo AuthorizeView, mas o conteúdo nunca é exibido.

Atributo [Authorize]
O atributo [Authorize] pode ser usado em componentes Razor:

razor

@page "/"
@attribute [Authorize]

You can only see this if you're signed in.

) Importante

Use [Authorize] somente em componentes @page acessados por meio do roteador


Blazor. A autorização é realizada apenas como um aspecto do roteamento e não
para componentes filho renderizados dentro de uma página. Para autorizar a
exibição de partes específicas dentro de uma página, use AuthorizeView.

O atributo [Authorize] também dá suporte à autorização baseada em funções ou em


políticas. Para a autorização baseada em funções, use o parâmetro Roles:

razor

@page "/"
@attribute [Authorize(Roles = "Admin, Superuser")]

<p>You can only see this if you're in the 'Admin' or 'Superuser' role.</p>

Para a autorização baseada em políticas, use o parâmetro Policy:

razor

@page "/"
@attribute [Authorize(Policy = "ContentEditor")]

<p>You can only see this if you satisfy the 'ContentEditor' policy.</p>

Se Roles e Policy não forem especificados, [Authorize] usará a política padrão, que
tratará:

Usuários autenticados (conectados) como autorizados.


Usuários não autenticados (não conectados) como não autorizados.

Autorização de recursos
Para autorizar usuários para recursos, passe os dados de rota da solicitação para o
parâmetro Resource de AuthorizeRouteView.

No conteúdo Router.Found de uma rota solicitada no componente App ( App.razor ):

razor

<AuthorizeRouteView Resource="@routeData" RouteData="@routeData"


DefaultLayout="@typeof(MainLayout)" />

Para obter mais informações sobre como os dados de estado de autorização são
passados e usados na lógica de procedimento, consulte a seção Expor o estado de
autenticação como um parâmetro em cascata.

Quando o AuthorizeRouteView recebe os dados de rota para o recurso, as políticas de


autorização têm acesso a RouteData.PageType e RouteData.RouteValues que permite
que a lógica personalizada tome decisões de autorização.

No exemplo a seguir, uma política EditUser é criada em AuthorizationOptions para a


configuração do serviço de autorização do aplicativo (AddAuthorizationCore) com a
seguinte lógica:

Determine se existe um valor de rota com uma chave de id . Se a chave existir, o


valor da rota será armazenado em value .
Em uma variável chamada id , armazene value como uma cadeia de caracteres ou
defina um valor de cadeia de caracteres vazio ( string.Empty ).
Se id não for uma cadeia de caracteres vazia, afirme que a política será atendida
(retornar true ) se o valor da cadeia de caracteres começar com EMP . Caso
contrário, declare que a política falhará (retornar false ).

Em Program.cs ou Startup.cs (dependendo do modelo de hospedagem e da versão da


estrutura):

Adicione namespaces para Microsoft.AspNetCore.Components e System.Linq:

C#

using Microsoft.AspNetCore.Components;
using System.Linq;

Adicione a política:

C#
options.AddPolicy("EditUser", policy =>
policy.RequireAssertion(context =>
{
if (context.Resource is RouteData rd)
{
var routeValue = rd.RouteValues.TryGetValue("id", out var
value);
var id = Convert.ToString(value,
System.Globalization.CultureInfo.InvariantCulture) ??
string.Empty;

if (!string.IsNullOrEmpty(id))
{
return id.StartsWith("EMP",
StringComparison.InvariantCulture);
}
}

return false;
})
);

O exemplo anterior é uma política de autorização simplificada, usada apenas para


demonstrar o conceito com um exemplo em funcionamento. Para obter mais
informações sobre como criar e configurar políticas de autorização, consulte
Autorização baseada em política no ASP.NET Core.

No componente EditUser a seguir, o recurso em /users/{id}/edit tem um parâmetro


de rota para o identificador do usuário ( {id} ). O componente usa a política de
autorização EditUser anterior para determinar se o valor da rota para id começa com
EMP . Se id começar com EMP , a política terá êxito e o acesso ao componente será
autorizado. Se id começar com um valor diferente EMP ou se id for uma cadeia de
caracteres vazia, a política falhará e o componente não será carregado.

Pages/EditUser.razor :

razor

@page "/users/{id}/edit"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "EditUser")]

<h1>Edit User</h1>

<p>The 'EditUser' policy is satisfied! <code>Id</code> starts with 'EMP'.


</p>

@code {
[Parameter]
public string Id { get; set; }
}

Personalizar conteúdo não autorizado com o


componente Router
O componente Router, em conjunto com o componente AuthorizeRouteView, permitirá
que o aplicativo especifique o conteúdo personalizado se:

O usuário não atender à condição [Authorize] aplicada ao componente. A


marcação do elemento <NotAuthorized> for exibida. O atributo [Authorize] é
abordado na seção Atributo [Authorize].
A autorização assíncrona estiver em andamento, o que geralmente significa que o
processo de autenticação do usuário está em andamento. A marcação do
elemento <Authorizing> for exibida.
O conteúdo não for encontrado. A marcação do elemento <NotFound> for
exibida.

No componente App ( App.razor ):

razor

<CascadingAuthenticationState>
<Router ...>
<Found ...>
<AuthorizeRouteView ...>
<NotAuthorized>
...
</NotAuthorized>
<Authorizing>
...
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView ...>
...
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

O conteúdo das marcas <NotFound> , <NotAuthorized> e <Authorizing> pode incluir itens


arbitrários, como outros componentes interativos.
Se a marca <NotAuthorized> não for especificada, o AuthorizeRouteView usará a
seguinte mensagem de fallback:

HTML

Not authorized.

Lógica de procedimento
Se for necessário que o aplicativo verifique as regras de autorização como parte da
lógica de procedimento, use um parâmetro em cascata do tipo
Task< AuthenticationState > para obter o ClaimsPrincipal do usuário.
Task< AuthenticationState > pode ser combinado com outros serviços, como

IAuthorizationService , para avaliar as políticas.

razor

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<button @onclick="@DoSomething">Do something important</button>

@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }

private async Task DoSomething()


{
var user = (await authenticationStateTask).User;

if (user.Identity.IsAuthenticated)
{
// Perform an action only available to authenticated (signed-in)
users.
}

if (user.IsInRole("admin"))
{
// Perform an action only available to users in the 'admin'
role.
}

if ((await AuthorizationService.AuthorizeAsync(user, "content-


editor"))
.Succeeded)
{
// Perform an action only available to users satisfying the
// 'content-editor' policy.
}
}
}

7 Observação

Em um componente de aplicativo Blazor WebAssembly, adicione os namespaces


Microsoft.AspNetCore.Authorization e
Microsoft.AspNetCore.Components.Authorization:

razor

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization

Esses namespaces podem ser fornecidos globalmente adicionando-os ao arquivo


_Imports.razor do aplicativo.

Solucionar problemas de erros


Erros comuns:

A autorização requer um parâmetro em cascata do tipo


Task<AuthenticationState> . Considere usar CascadingAuthenticationState para

fornecer isso.

O valor null é recebido para authenticationStateTask

É provável que o projeto não tenha sido criado usando um modelo Blazor Server com a
autenticação habilitada. Encapsule um <CascadingAuthenticationState> em torno de
alguma parte da árvore da interface do usuário, por exemplo, no componente App
( App.razor ), da seguinte maneira:

razor

<CascadingAuthenticationState>
<Router ...>
...
</Router>
</CascadingAuthenticationState>

O CascadingAuthenticationState fornece o parâmetro em cascata


Task< AuthenticationState > que, por sua vez, ele recebe do serviço DI subjacente
AuthenticationStateProvider.

Recursos adicionais
Documentação da plataforma de identidade da Microsoft
Visão geral
Protocolos do OAuth 2.0 e do OpenID Connect na plataforma de identidade da
Microsoft
Plataforma de identidade da Microsoft e fluxo de código de autorização do
OAuth 2.0
Tokens de ID da plataforma de identidade da Microsoft
Tokens de acesso da plataforma de identidade da Microsoft
Tópicos de segurança do ASP.NET Core
Configurar a Autenticação do Windows no ASP.NET Core
Criar uma versão personalizada da biblioteca JavaScript Authentication.MSAL
Autenticação e autorização de Blazor Hybrid no ASP.NET Core
Awesome Blazor: links de exemplo da comunidade de autenticação
Proteger aplicativos Blazor Server do
ASP.NET Core
Artigo • 28/11/2022 • 16 minutos para o fim da leitura

Este artigo explica como proteger aplicativos Blazor Server como aplicativos ASP.NET
Core.

Aplicativos Blazor Server são configurados para segurança da mesma maneira que
aplicativos ASP.NET Core. Para obter mais informações, consulte os artigos em Tópicos
de segurança do ASP.NET Core. Os tópicos nesta visão geral se aplicam especificamente
a Blazor Server.

Modelo de projeto Blazor Server


O modelo de projeto Blazor Server pode ser configurado para autenticação quando o
projeto é criado.

Visual Studio

Siga as diretrizes do Visual Studio em Ferramentas para o ASP.NET Core Blazor para
criar um projeto Blazor Server com um mecanismo de autenticação.

Após escolher o modelo Aplicativo Blazor Server na caixa de diálogo Criar um


aplicativo Web ASP.NET Core, selecione Alterar em Autenticação.

Uma caixa de diálogo é aberta para oferecer o mesmo conjunto de mecanismos de


autenticação para outros projetos ASP.NET Core:

Sem Autenticação
Contas de usuário individuais: as contas de usuário podem ser armazenadas:
Dentro do aplicativo usando o sistema Identity do ASP.NET Core.
Com o Azure AD B2C.
Contas corporativas ou de estudante
Autenticação do Windows

Scaffold Identity
Para obter mais informações sobre scaffolding de Identity em um projeto Blazor Server,
consulte Scaffold de Identity em projetos do ASP.NET Core.

Declarações e tokens adicionais de provedores


externos
Para armazenar declarações adicionais de provedores externos, consulte Persistir
declarações e tokens adicionais de provedores externos no ASP.NET Core.

Serviço de Aplicativo do Azure no Linux com o


Identity Server
Especifique o emissor explicitamente ao implantar no Serviço de Aplicativo do Azure no
Linux com o Identity Server. Para obter mais informações, consulte Introdução à
autenticação para Aplicativos de Página Única no ASP.NET Core.

Notificação sobre mudanças no estado de


autenticação
Se o aplicativo determinar que os dados sobre o estado de autenticação subjacente
foram alterados (por exemplo, porque o usuário se desconectou ou porque outro
usuário alterou suas funções), um AuthenticationStateProvider personalizado poderá
invocar opcionalmente o método NotifyAuthenticationStateChanged na classe base
AuthenticationStateProvider. Isso notificará os consumidores a respeito dos dados de
estado de autenticação (por exemplo, AuthorizeView) para realizar uma nova
renderização usando os novos dados.

Implementar um AuthenticationStateProvider
personalizado
Se o aplicativo exigir um provedor personalizado, implemente
AuthenticationStateProvider e substitua GetAuthenticationStateAsync :

C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class CustomAuthStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "mrfibuli"),
}, "Fake authentication type");

var user = new ClaimsPrincipal(identity);

return Task.FromResult(new AuthenticationState(user));


}
}

O serviço CustomAuthStateProvider é registrado Program.cs após a chamada para


AddServerSideBlazor:

C#

using Microsoft.AspNetCore.Components.Authorization;

...

builder.Services.AddServerSideBlazor();

...

builder.Services.TryAddScoped<AuthenticationStateProvider,
CustomAuthStateProvider>();

Usando o CustomAuthStateProvider no exemplo anterior, todos os usuários são


autenticados com o nome de usuário mrfibuli .

Recursos adicionais
Início Rápido: Adicionar entrada com a Microsoft para um aplicativo Web do
ASP.NET Core
Início Rápido: Proteger uma API Web ASP.NET Core com a plataforma de
identidade da Microsoft
Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores
de carga: inclui diretrizes sobre:
O uso de Middleware de Cabeçalhos Encaminhados para preservar informações
do esquema HTTPS entre servidores proxy e redes internas.
Cenários e casos de uso adicionais, incluindo configuração de esquema manual,
solicitação de alterações de caminho para roteamento de solicitação correto e
encaminhamento do esquema de solicitação para proxies reversos do Linux e
não IIS.
Diretrizes de mitigação de ameaças para
ASP.NET Core Blazor Server
Artigo • 06/01/2023 • 72 minutos para o fim da leitura

Este artigo explica como atenuar as ameaças de segurança aos Blazor Server aplicativos.

Blazor Server os aplicativos adotam um modelo de processamento de dados com estado


, em que o servidor e o cliente mantêm uma relação de longa duração. O estado
persistente é mantido por um circuito, que pode abranger conexões que também são
potencialmente de longa duração.

Quando um usuário visita um Blazor Server site, o servidor cria um circuito na memória
do servidor. O circuito indica ao navegador qual conteúdo renderizar e responde a
eventos, como quando o usuário seleciona um botão na interface do usuário. Para
executar essas ações, um circuito invoca funções JavaScript no navegador do usuário e
métodos .NET no servidor. Essa interação bidirecional baseada em JavaScript é
conhecida como interoperabilidade javaScript (JS interoperabilidade).

Como JS a interoperabilidade ocorre pela Internet e o cliente usa um navegador remoto,


Blazor Server os aplicativos compartilham a maioria das preocupações de segurança do
aplicativo Web. Este tópico descreve ameaças comuns a aplicativos e fornece diretrizes
de mitigação de ameaças focadas em aplicativos voltados para Blazor Server a Internet.

Em ambientes restritos, como dentro de redes corporativas ou intranets, algumas das


diretrizes de mitigação:

Não se aplica no ambiente restrito.


Não vale o custo a ser implementado porque o risco de segurança é baixo em um
ambiente restrito.

Blazor e estado compartilhado


Blazor aplicativos de servidor ativos na memória do servidor. Isso significa que há vários
aplicativos hospedados no mesmo processo. Para cada sessão de aplicativo, Blazor inicia
um circuito com seu próprio escopo de contêiner de DI. Isso significa que os serviços
com escopo são exclusivos por Blazor sessão.

2 Aviso
Não recomendamos que os aplicativos no mesmo estado de compartilhamento de
servidor usem serviços singleton, a menos que sejam tomados cuidados extremos,
pois isso pode introduzir vulnerabilidades de segurança, como o vazamento do
estado do usuário entre circuitos.

Você pode usar serviços singleton com estado em Blazor aplicativos se eles forem
projetados especificamente para ele. Por exemplo, não há problema em usar um cache
de memória como singleton porque ele requer uma chave para acessar uma
determinada entrada, supondo que os usuários não tenham controle de quais chaves de
cache são usadas.

Além disso, novamente por motivos de segurança, você não deve usar dentro Blazor
de IHttpContextAccessor aplicativos. Blazoros aplicativos são executados fora do
contexto do pipeline de ASP.NET Core. Não HttpContext há garantia de que o
IHttpContextAccessoresteja disponível no e HttpContext não tem garantia de manter o
contexto que iniciou o Blazor aplicativo. Para obter mais informações, consulte
Implicações de segurança do uso IHttpContextAccessor no Blazor Server
(dotnet/aspnetcore #45699) .

A maneira recomendada de passar o estado da solicitação para o Blazor aplicativo é por


meio de parâmetros para o componente raiz na renderização inicial do aplicativo:

Defina uma classe com todos os dados que você deseja passar para o Blazor
aplicativo.
Preencha esses dados da Razor página usando o HttpContext disponível no
momento.
Passe os dados para o Blazor aplicativo como um parâmetro para o componente
raiz (App).
Defina um parâmetro no componente raiz para manter os dados que estão sendo
passados para o aplicativo.
Use os dados específicos do usuário no aplicativo; ou, como alternativa, copie
esses dados em um serviço com escopo dentro OnInitializedAsync para que eles
possam ser usados em todo o aplicativo.

Para obter mais informações e código de exemplo, consulte ASP.NET Core Blazor Server
cenários de segurança adicionais.

Esgotamento de recursos
O esgotamento de recursos pode ocorrer quando um cliente interage com o servidor e
faz com que o servidor consuma recursos excessivos. O consumo excessivo de recursos
afeta principalmente:

CPU
Memória
Conexões de cliente

Ataques de Negação de Serviço (DoS) geralmente buscam esgotar os recursos de um


aplicativo ou servidor. No entanto, o esgotamento de recursos não é necessariamente o
resultado de um ataque ao sistema. Por exemplo, recursos finitos podem ser esgotados
devido à alta demanda do usuário. O DoS é abordado ainda mais na seção Ataques de
Negação de Serviço (DoS ).

Recursos externos à Blazor estrutura, como bancos de dados e identificadores de


arquivo (usados para ler e gravar arquivos), também podem ter esgotamento de
recursos. Para obter mais informações, consulte ASP.NET Core Práticas Recomendadas.

CPU
O esgotamento da CPU pode ocorrer quando um ou mais clientes forçam o servidor a
executar um trabalho intensivo de CPU.

Por exemplo, considere um Blazor Server aplicativo que calcula um número fibonnacci.
Um número fibonnacci é produzido a partir de uma sequência de Fibonnacci, em que
cada número na sequência é a soma dos dois números anteriores. A quantidade de
trabalho necessária para alcançar a resposta depende do comprimento da sequência e
do tamanho do valor inicial. Se o aplicativo não colocar limites na solicitação de um
cliente, os cálculos com uso intensivo de CPU poderão dominar o tempo da CPU e
diminuir o desempenho de outras tarefas. O consumo excessivo de recursos é uma
preocupação de segurança que afeta a disponibilidade.

O esgotamento da CPU é uma preocupação para todos os aplicativos voltados para o


público. Em aplicativos Web regulares, as solicitações e as conexões atingiram o tempo
limite como uma proteção, mas Blazor Server os aplicativos não fornecem as mesmas
proteções. Blazor Server os aplicativos devem incluir verificações e limites apropriados
antes de executar um trabalho potencialmente intensivo de CPU.

Memória
O esgotamento de memória pode ocorrer quando um ou mais clientes forçam o
servidor a consumir uma grande quantidade de memória.

Por exemplo, considere um Blazoraplicativo do lado do servidor com um componente


que aceita e exibe uma lista de itens. Se o Blazor aplicativo não colocar limites no
número de itens permitidos ou no número de itens renderizados de volta para o cliente,
o processamento e a renderização com uso intensivo de memória poderão dominar a
memória do servidor até o ponto em que o desempenho do servidor sofre. O servidor
pode falhar ou ficar lento até o ponto em que parece ter falhado.

Considere o cenário a seguir para manter e exibir uma lista de itens que pertencem a
um possível cenário de esgotamento de memória no servidor:

Os itens em uma List<MyItem> propriedade ou campo usam a memória do


servidor. Se o aplicativo permitir que a lista de itens cresça sem associação, há o
risco de o servidor ficar sem memória. Ficar sem memória faz com que a sessão
atual termine (falha) e todas as sessões simultâneas nessa instância do servidor
recebem uma exceção de memória insuficiente. Para evitar que esse cenário
ocorra, o aplicativo deve usar uma estrutura de dados que imponha um limite de
itens aos usuários simultâneos.
Se um esquema de paginação não for usado para renderização, o servidor usará
memória adicional para objetos que não estão visíveis na interface do usuário.
Sem um limite no número de itens, as demandas de memória podem esgotar a
memória do servidor disponível. Para evitar esse cenário, use uma das seguintes
abordagens:
Use listas paginadas ao renderizar.
Exiba apenas os primeiros 100 a 1.000 itens e exija que o usuário insira critérios
de pesquisa para localizar itens além dos itens exibidos.
Para um cenário de renderização mais avançado, implemente listas ou grades
que dão suporte à virtualização. Usando a virtualização, as listas renderizam
apenas um subconjunto de itens atualmente visíveis para o usuário. Quando o
usuário interage com a barra de rolagem na interface do usuário, o componente
renderiza apenas os itens necessários para exibição. Os itens que atualmente
não são necessários para exibição podem ser mantidos no armazenamento
secundário, que é a abordagem ideal. Itens não reproduzidos também podem
ser mantidos na memória, o que é menos ideal.

Blazor Serveros aplicativos oferecem um modelo de programação semelhante a outras


estruturas de interface do usuário para aplicativos com estado, como WPF, Windows
Forms ou Blazor WebAssembly. A principal diferença é que, em várias das estruturas de
interface do usuário, a memória consumida pelo aplicativo pertence ao cliente e afeta
apenas esse cliente individual. Por exemplo, um Blazor WebAssembly aplicativo é
executado inteiramente no cliente e usa apenas recursos de memória do cliente. Blazor
Server No cenário, a memória consumida pelo aplicativo pertence ao servidor e é
compartilhada entre os clientes na instância do servidor.
As demandas de memória do lado do servidor são uma consideração para todos os
Blazor Server aplicativos. No entanto, a maioria dos aplicativos Web são sem estado e a
memória usada durante o processamento de uma solicitação é liberada quando a
resposta é retornada. Como recomendação geral, não permita que os clientes aloquem
uma quantidade de memória desassociada como em qualquer outro aplicativo do lado
do servidor que persista as conexões do cliente. A memória consumida por um Blazor
Server aplicativo persiste por mais tempo do que uma única solicitação.

7 Observação

Durante o desenvolvimento, um criador de perfil pode ser usado ou um


rastreamento capturado para avaliar as demandas de memória dos clientes. Um
criador de perfil ou rastreamento não capturará a memória alocada a um cliente
específico. Para capturar o uso de memória de um cliente específico durante o
desenvolvimento, capture um despejo e examine a demanda de memória de todos
os objetos com raiz no circuito de um usuário.

Conexões de cliente
O esgotamento da conexão pode ocorrer quando um ou mais clientes abrem muitas
conexões simultâneas com o servidor, impedindo que outros clientes estabeleçam
novas conexões.

Blazor os clientes estabelecem uma única conexão por sessão e mantêm a conexão
aberta enquanto a janela do navegador estiver aberta. As demandas no servidor de
manter todas as conexões não são específicas para Blazor aplicativos. Dada a natureza
persistente das conexões e a natureza com estado dos aplicativos, o esgotamento da
Blazor Server conexão é um risco maior para a disponibilidade do aplicativo.

Por padrão, não há limite no número de conexões por usuário para um Blazor Server
aplicativo. Se o aplicativo exigir um limite de conexão, siga uma ou mais das seguintes
abordagens:

Exigir autenticação, o que naturalmente limita a capacidade de usuários não


autorizados se conectarem ao aplicativo. Para que esse cenário seja eficaz, os
usuários devem ser impedidos de provisionar novos usuários sob demanda.
Limite o número de conexões por usuário. A limitação de conexões pode ser
realizada por meio das abordagens a seguir. Tenha cuidado para permitir que
usuários legítimos acessem o aplicativo (por exemplo, quando um limite de
conexão é estabelecido com base no endereço IP do cliente).
No nível do aplicativo:
Extensibilidade de roteamento de ponto de extremidade.
Exigir autenticação para se conectar ao aplicativo e acompanhar as sessões
ativas por usuário.
Rejeite novas sessões ao atingir um limite.
Conexões WebSocket de proxy com um aplicativo por meio do uso de um
proxy, como o Serviço do Azure SignalR que multiplexa conexões de clientes
para um aplicativo. Isso fornece um aplicativo com maior capacidade de
conexão do que um único cliente pode estabelecer, impedindo que um
cliente esgote as conexões com o servidor.

No nível do servidor: use um proxy/gateway na frente do aplicativo. Por


exemplo, o Azure Front Door permite que você defina, gerencie e monitore o
roteamento global do tráfego da Web para um aplicativo e funciona quando
Blazor Server os aplicativos são configurados para usar a Sondagem Longa.

7 Observação

Embora haja suporte para sondagem longa para Blazor Server aplicativos,
WebSockets é o protocolo de transporte recomendado. O Azure Front
Door não dá suporte a WebSockets no momento, mas o suporte para
WebSockets está em consideração para uma versão futura do serviço.

Ataques de DoS (negação de serviço)


Ataques de DoS (negação de serviço) envolvem um cliente fazendo com que o
servidor esgote um ou mais de seus recursos tornando o aplicativo indisponível. Blazor
ServerOs aplicativos incluem limites padrão e dependem de outras ASP.NET Core e
SignalR limites definidos CircuitOptions para proteger contra ataques do DoS:

CircuitOptions.DisconnectedCircuitMaxRetained
CircuitOptions.DisconnectedCircuitRetentionPeriod
CircuitOptions.JSInteropDefaultCallTimeout
CircuitOptions.MaxBufferedUnacknowledgedRenderBatches
HubConnectionContextOptions.MaximumReceiveMessageSize

Para obter mais informações e exemplos de codificação de configuração, consulte os


seguintes artigos:

diretrizes BlazorSignalR de ASP.NET Core


Configuração do ASP.NET Core SignalR
Interações com o navegador (cliente)
Um cliente interage com o servidor por meio JS da expedição de eventos de
interoperabilidade e da conclusão da renderização. JS A comunicação de
interoperabilidade vai para os dois lados entre JavaScript e .NET:

Os eventos do navegador são enviados do cliente para o servidor de forma


assíncrona.
O servidor responde de forma assíncrona rerender a interface do usuário conforme
necessário.

Funções JavaScript invocadas do .NET


Para chamadas de métodos .NET para JavaScript:

Todas as invocações têm um tempo limite configurável após o qual falham,


retornando um OperationCanceledException para o chamador.
Há um tempo limite padrão para as chamadas
(CircuitOptions.JSInteropDefaultCallTimeout) de um minuto. Para configurar
esse limite, consulte Chamar funções JavaScript de métodos .NET no ASP.NET
Core Blazor.
Um token de cancelamento pode ser fornecido para controlar o cancelamento
por chamada. Dependa do tempo limite de chamada padrão sempre que
possível e limite de tempo de qualquer chamada ao cliente se um token de
cancelamento for fornecido.
O resultado de uma chamada JavaScript não pode ser confiável. O Blazor cliente
do aplicativo em execução no navegador procura a função JavaScript a ser
invocada. A função é invocada e o resultado ou um erro é produzido. Um cliente
mal-intencionado pode tentar:
Cause um problema no aplicativo retornando um erro da função JavaScript.
Induza um comportamento não intencional no servidor retornando um
resultado inesperado da função JavaScript.

Tome as seguintes precauções para se proteger contra os cenários anteriores:

Encapsule JS as chamadas de interoperabilidade em try-catch instruções para


considerar erros que podem ocorrer durante as invocações. Para obter mais
informações, consulte Tratar erros em aplicativos ASP.NET CoreBlazor.
Valide os dados retornados de invocações de JS interoperabilidade, incluindo
mensagens de erro, antes de executar qualquer ação.
Métodos .NET invocados do navegador
Não confie em chamadas de JavaScript para métodos .NET. Quando um método .NET é
exposto ao JavaScript, considere como o método .NET é invocado:

Trate qualquer método .NET exposto ao JavaScript como faria com um ponto de
extremidade público para o aplicativo.
Validar entrada.
Verifique se os valores estão dentro dos intervalos esperados.
Verifique se o usuário tem permissão para executar a ação solicitada.
Não aloque uma quantidade excessiva de recursos como parte da invocação do
método .NET. Por exemplo, execute verificações e coloque limites no uso de
CPU e memória.
Leve em conta que os métodos estáticos e de instância podem ser expostos a
clientes JavaScript. Evite compartilhar o estado entre sessões, a menos que o
design chame o estado de compartilhamento com as restrições apropriadas.
Por exemplo, métodos expostos por meio DotNetReference de objetos que
são criados originalmente por meio de DI (injeção de dependência), os
objetos devem ser registrados como objetos com escopo. Isso se aplica a
qualquer serviço de DI que o Blazor Server aplicativo usa.
Para métodos estáticos, evite estabelecer um estado que não possa ser
definido como escopo para o cliente, a menos que o aplicativo esteja
compartilhando explicitamente o estado por design entre todos os usuários
em uma instância de servidor.
Evite passar dados fornecidos pelo usuário em parâmetros para chamadas
JavaScript. Se passar dados em parâmetros for absolutamente necessário,
verifique se o código JavaScript lida com a passagem dos dados sem introduzir
vulnerabilidades de XSS (cross-site scripting ). Por exemplo, não escreva dados
fornecidos pelo usuário no DOM (Modelo de Objeto do Documento) definindo
a innerHTML propriedade de um elemento. Considere usar a CSP (Política de
Segurança de Conteúdo) para desabilitar eval e outros primitivos JavaScript
não seguros.
Evite implementar a expedição personalizada de invocações do .NET sobre a
implementação de expedição da estrutura. Expor métodos .NET ao navegador é
um cenário avançado, não recomendado para desenvolvimento geral Blazor .

Eventos
Os eventos fornecem um ponto de entrada para um Blazor Server aplicativo. As mesmas
regras para proteger pontos de extremidade em aplicativos Web se aplicam à
manipulação de eventos em Blazor Server aplicativos. Um cliente mal-intencionado
pode enviar todos os dados que deseja enviar como o conteúdo de um evento.

Por exemplo:

Um evento de alteração para um <select> pode enviar um valor que não está
dentro das opções que o aplicativo apresentou ao cliente.
Um <input> pode enviar qualquer dado de texto para o servidor, ignorando a
validação do lado do cliente.

O aplicativo deve validar os dados para qualquer evento que o aplicativo manipule. Os
Blazorcomponentes de formulários de estrutura executam validações básicas. Se o
aplicativo usar componentes de formulários personalizados, o código personalizado
deverá ser gravado para validar os dados do evento conforme apropriado.

Blazor Server os eventos são assíncronos, portanto, vários eventos podem ser enviados
para o servidor antes que o aplicativo tenha tempo para reagir produzindo uma nova
renderização. Isso tem algumas implicações de segurança a serem consideradas. A
limitação das ações do cliente no aplicativo deve ser executada dentro de
manipuladores de eventos e não depender do estado de exibição renderizado atual.

Considere um componente de contador que deve permitir que um usuário incremente


um contador no máximo três vezes. O botão para incrementar o contador é
condicionalmente baseado no valor de count :

razor

<p>Count: @count<p>

@if (count < 3)


{
<button @onclick="IncrementCount" value="Increment count" />
}

@code
{
private int count = 0;

private void IncrementCount()


{
count++;
}
}

Um cliente pode expedir um ou mais eventos de incremento antes que a estrutura


produza uma nova renderização desse componente. O resultado é que o count pode
ser incrementado mais de três vezes pelo usuário porque o botão não é removido pela
interface do usuário rapidamente o suficiente. A maneira correta de atingir o limite de
três count incrementos é mostrada no exemplo a seguir:

razor

<p>Count: @count<p>

@if (count < 3)


{
<button @onclick="IncrementCount" value="Increment count" />
}

@code
{
private int count = 0;

private void IncrementCount()


{
if (count < 3)
{
count++;
}
}
}

Ao adicionar a if (count < 3) { ... } verificação dentro do manipulador, a decisão de


incrementar count é baseada no estado atual do aplicativo. A decisão não se baseia no
estado da interface do usuário como era no exemplo anterior, que pode estar
temporariamente obsoleto.

Proteger-se contra várias expedições


Se um retorno de chamada de evento invocar uma operação de execução prolongada
de forma assíncrona, como buscar dados de um serviço ou banco de dados externo,
considere o uso de uma proteção. A proteção pode impedir que o usuário enfileira
várias operações enquanto a operação está em andamento com comentários visuais. O
código de componente a true seguir define isLoading como enquanto
GetForecastAsync obtém dados do servidor. Enquanto isLoading é true , o botão está
desabilitado na interface do usuário:

razor

@page "/fetchdata"
@using BlazorServerSample.Data
@inject WeatherForecastService ForecastService
<button disabled="@isLoading" @onclick="UpdateForecasts">Update</button>

@code {
private bool isLoading;
private WeatherForecast[] forecasts;

private async Task UpdateForecasts()


{
if (!isLoading)
{
isLoading = true;
forecasts = await
ForecastService.GetForecastAsync(DateTime.Now);
isLoading = false;
}
}
}

O padrão de proteção demonstrado no exemplo anterior funcionará se a operação em


segundo plano for executada de forma assíncrona com o async - await padrão .

Cancelar antecipadamente e evitar o uso após o descarte


Além de usar uma proteção conforme descrito na seção Proteger contra várias
expedições , considere usar um CancellationToken para cancelar operações de execução
longa quando o componente for descartado. Essa abordagem tem o benefício adicional
de evitar o uso após o descarte em componentes:

razor

@implements IDisposable

...

@code {
private readonly CancellationTokenSource TokenSource =
new CancellationTokenSource();

private async Task UpdateForecasts()


{
...

forecasts = await ForecastService.GetForecastAsync(DateTime.Now,


TokenSource.Token);

if (TokenSource.Token.IsCancellationRequested)
{
return;
}
...
}

public void Dispose()


{
TokenSource.Cancel();
}
}

Evitar eventos que produzem grandes quantidades de


dados
Alguns eventos DOM, como oninput ou onscroll , podem produzir uma grande
quantidade de dados. Evite usar esses eventos em Blazor aplicativos de servidor.

Diretrizes de segurança adicionais


As diretrizes para proteger aplicativos ASP.NET Core se aplicam a Blazor Server
aplicativos e são abordadas nas seguintes seções:

Registro em log e dados confidenciais


Proteger informações em trânsito com HTTPS
XSS (script entre sites)
Proteção entre origens
Click-jacking
Abrir redirecionamentos

Registro em log e dados confidenciais


JS As interações de interoperabilidade entre o cliente e o servidor são registradas nos
logs do servidor com ILogger instâncias. Blazor evita o registro em log de informações
confidenciais, como eventos reais ou JS entradas e saídas de interoperabilidade.

Quando ocorre um erro no servidor, a estrutura notifica o cliente e rasga a sessão. Por
padrão, o cliente recebe uma mensagem de erro genérica que pode ser vista nas
ferramentas de desenvolvedor do navegador.

O erro do lado do cliente não inclui a pilha de chamadas e não fornece detalhes sobre a
causa do erro, mas os logs do servidor contêm essas informações. Para fins de
desenvolvimento, informações de erro confidenciais podem ser disponibilizadas para o
cliente habilitando erros detalhados.
2 Aviso

Expor informações de erro a clientes na Internet é um risco de segurança que


sempre deve ser evitado.

Proteger informações em trânsito com HTTPS


Blazor Server usa SignalR para comunicação entre o cliente e o servidor. Blazor Server
normalmente usa o transporte que SignalR negocia, que normalmente é WebSockets.

Blazor Server não garante a integridade e a confidencialidade dos dados enviados entre
o servidor e o cliente. Sempre use HTTPS.

XSS (script entre sites)


O XSS (script entre sites) permite que uma parte não autorizada execute uma lógica
arbitrária no contexto do navegador. Um aplicativo comprometido pode
potencialmente executar código arbitrário no cliente. A vulnerabilidade pode ser usada
para executar potencialmente uma série de ações mal-intencionadas no servidor:

Envie eventos falsos/inválidos para o servidor.


Falha de expedição/conclusões de renderização inválidas.
Evite expedir conclusões de renderização.
Enviar chamadas de interoperabilidade do JavaScript para o .NET.
Modifique a resposta de chamadas de interoperabilidade do .NET para JavaScript.
Evite expedir o .NET para JS resultados de interoperabilidade.

A Blazor Server estrutura executa etapas para proteger contra algumas das ameaças
anteriores:

Para de produzir novas atualizações de interface do usuário se o cliente não estiver


reconhecendo lotes de renderização. Configurado com
CircuitOptions.MaxBufferedUnacknowledgedRenderBatches.
Atinge o tempo limite de qualquer chamada .NET para JavaScript após um minuto
sem receber uma resposta do cliente. Configurado com
CircuitOptions.JSInteropDefaultCallTimeout.
Executa a validação básica em todas as entradas provenientes do navegador
durante JS a interoperabilidade:
As referências do .NET são válidas e do tipo esperado pelo método .NET.
Os dados não estão malformados.
O número correto de argumentos para o método está presente no conteúdo.
Os argumentos ou o resultado podem ser desserializados corretamente antes
de invocar o método .
Executa a validação básica em todas as entradas provenientes do navegador de
eventos expedidos:
O evento tem um tipo válido.
Os dados do evento podem ser desserializados.
Há um manipulador de eventos associado ao evento.

Além das proteções implementadas pela estrutura, o aplicativo deve ser codificado pelo
desenvolvedor para proteger contra ameaças e tomar as ações apropriadas:

Sempre valide os dados ao manipular eventos.


Tome as medidas apropriadas ao receber dados inválidos:
Ignore os dados e retorne. Isso permite que o aplicativo continue processando
solicitações.
Se o aplicativo determinar que a entrada é ilegítima e não pôde ser produzida
pelo cliente legítimo, gere uma exceção. Lançar uma exceção rasga o circuito e
encerra a sessão.
Não confie na mensagem de erro fornecida por conclusões em lotes de
renderização incluídas nos logs. O erro é fornecido pelo cliente e geralmente não
pode ser confiável, pois o cliente pode ser comprometido.
Não confie na entrada em JS chamadas de interoperabilidade em nenhuma das
direções entre os métodos JavaScript e .NET.
O aplicativo é responsável por validar que o conteúdo de argumentos e resultados
é válido, mesmo que os argumentos ou resultados sejam desserializados
corretamente.

Para que uma vulnerabilidade XSS exista, o aplicativo deve incorporar a entrada do
usuário na página renderizada. Blazor Server os componentes executam uma etapa de
tempo de compilação em que a marcação em um .razor arquivo é transformada em
lógica C# de procedimento. Em runtime, a lógica C# cria uma árvore de renderização
que descreve os elementos, o texto e os componentes filho. Isso é aplicado ao DOM do
navegador por meio de uma sequência de instruções JavaScript (ou é serializado para
HTML no caso de pré-geração):

A entrada do usuário renderizada por meio da sintaxe normal Razor (por exemplo,
@someStringValue ) não expõe uma vulnerabilidade XSS porque a Razor sintaxe é

adicionada ao DOM por meio de comandos que só podem gravar texto. Mesmo
que o valor inclua marcação HTML, o valor será exibido como texto estático. Ao
pré-gerar, a saída é codificada em HTML, que também exibe o conteúdo como
texto estático.
As marcas de script não são permitidas e não devem ser incluídas na árvore de
renderização de componentes do aplicativo. Se uma marca de script estiver
incluída na marcação de um componente, um erro em tempo de compilação será
gerado.
Os autores de componentes podem criar componentes em C# sem usar Razor. O
autor do componente é responsável por usar as APIs corretas ao emitir a saída. Por
exemplo, use builder.AddContent(0, someUserSuppliedString) e
não builder.AddMarkupContent(0, someUserSuppliedString) , pois este último pode
criar uma vulnerabilidade XSS.

Como parte da proteção contra ataques XSS, considere implementar mitigações XSS,
como a Política de Segurança de Conteúdo (CSP) .

Para obter mais informações, consulte Impedir XSS (script entre sites) em ASP.NET Core.

Proteção entre origens


Ataques entre origens envolvem um cliente de uma origem diferente executando uma
ação no servidor. A ação mal-intencionada normalmente é uma solicitação GET ou um
formulário POST (Solicitação Intersite Forjada, CSRF), mas abrir um WebSocket mal-
intencionado também é possível. Blazor Server os aplicativos oferecem as mesmas
garantias de que qualquer outro SignalR aplicativo que usa a oferta de protocolo de
hub:

Blazor Server os aplicativos podem ser acessados entre origens, a menos que
medidas adicionais sejam tomadas para impedi-lo. Para desabilitar o acesso entre
origens, desabilite o CORS no ponto de extremidade adicionando o middleware
CORS ao pipeline e adicionando o DisableCorsAttribute aos Blazor metadados do
ponto de extremidade ou limite o conjunto de origens permitidas configurando
SignalR para compartilhamento de recursos entre origens.
Se o CORS estiver habilitado, etapas adicionais poderão ser necessárias para
proteger o aplicativo, dependendo da configuração do CORS. Se o CORS estiver
habilitado globalmente, o CORS poderá ser desabilitado para o hub adicionando
Blazor Server os DisableCorsAttribute metadados aos metadados do ponto de
extremidade depois de chamar MapBlazorHub no construtor de rotas do ponto de
extremidade.

Para obter mais informações, consulte Impedir ataques de XSRF/CSRF (solicitação


intersite forjada) no ASP.NET Core.

Click-jacking
O clique envolve renderizar um site como um <iframe> dentro de um site de uma
origem diferente, a fim de enganar o usuário para executar ações no site sob ataque.

Para proteger um aplicativo contra renderização dentro de um <iframe> , use a CSP


(Política de Segurança de Conteúdo) e o X-Frame-Options cabeçalho . Para obter mais
informações, consulte Documentos da Web do MDN: X-Frame-Options .

Abrir redirecionamentos
Quando uma Blazor Server sessão de aplicativo é iniciada, o servidor executa a validação
básica das URLs enviadas como parte do início da sessão. A estrutura verifica se a URL
base é um pai da URL atual antes de estabelecer o circuito. Nenhuma verificação
adicional é executada pela estrutura.

Quando um usuário seleciona um link no cliente, a URL do link é enviada ao servidor,


que determina qual ação deve ser tomada. Por exemplo, o aplicativo pode executar uma
navegação do lado do cliente ou indicar ao navegador para ir para o novo local.

Os componentes também podem disparar solicitações de navegação


programaticamente por meio do uso de NavigationManager. Nesses cenários, o
aplicativo pode executar uma navegação do lado do cliente ou indicar ao navegador
para ir para o novo local.

Os componentes devem:

Evite usar a entrada do usuário como parte dos argumentos de chamada de


navegação.
Valide argumentos para garantir que o destino seja permitido pelo aplicativo.

Caso contrário, um usuário mal-intencionado pode forçar o navegador a ir para um site


controlado pelo invasor. Nesse cenário, o invasor engana o aplicativo para usar alguma
entrada do usuário como parte da invocação do NavigationManager.NavigateTo
método .

Esse conselho também se aplica ao renderizar links como parte do aplicativo:

Se possível, use links relativos.


Valide se os destinos de link absoluto são válidos antes de incluí-los em uma
página.

Para obter mais informações, consulte Impedir ataques de redirecionamento abertos em


ASP.NET Core.
Lista de verificação de segurança
A lista a seguir de considerações de segurança não é abrangente:

Validar argumentos de eventos.


Validar entradas e resultados de chamadas de JS interoperabilidade.
Evite usar (ou validar antecipadamente) a entrada do usuário para .NET para JS
interoperabilidade de chamadas.
Impedir que o cliente aloque uma quantidade de memória não associado.
Dados dentro do componente.
DotNetObject referências retornadas ao cliente.
Proteja-se contra várias expedições.
Cancele operações de execução longa quando o componente for descartado.
Evite eventos que produzam grandes quantidades de dados.
Evite usar a entrada do usuário como parte de chamadas para
NavigationManager.NavigateTo e valide a entrada do usuário para URLs em
relação a um conjunto de origens permitidas primeiro, se inevitável.
Não tome decisões de autorização com base no estado da interface do usuário,
mas apenas no estado do componente.
Considere usar a CSP (Política de Segurança de Conteúdo) para proteger contra
ataques XSS.
Considere usar CSP e X-Frame-Options para proteger contra cliques.
Verifique se as configurações de CORS são apropriadas ao habilitar o CORS ou
desabilitar explicitamente o CORS para Blazor aplicativos.
Teste para garantir que os limites do lado do servidor para o Blazor aplicativo
forneçam uma experiência aceitável do usuário sem níveis inaceitáveis de risco.
Cenários de segurança adicionais do
ASP.NET Core Blazor Server
Artigo • 10/01/2023 • 8 minutos para o fim da leitura

Este artigo explica como configurar Blazor Server cenários de segurança adicionais,
incluindo como passar tokens para um Blazor Server aplicativo.

Passar tokens para um Blazor Server aplicativo


Tokens disponíveis fora dos Razor componentes em um Blazor Server aplicativo podem
ser passados para componentes com a abordagem descrita nesta seção.

Autentique o Blazor Server aplicativo como faria com um aplicativo MVC ou Páginas
regulares Razor . Provisione e salve os tokens na autenticação cookie. Por exemplo:

C#

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;

options.Scope.Add("offline_access");
});

Opcionalmente, escopos adicionais são adicionados com options.Scope.Add("


{SCOPE}"); , em que o espaço reservado {SCOPE} é o escopo adicional a ser adicionado.

Defina um serviço de provedor de token com escopo que pode ser usado dentro do
Blazor aplicativo para resolver os tokens da DI (injeção de dependência):

C#

public class TokenProvider


{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
}
No Program.cs , adicione serviços para:

IHttpClientFactory
TokenProvider

C#

builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();

Defina uma classe para passar o estado inicial do aplicativo com os tokens de acesso e
atualização:

C#

public class InitialApplicationState


{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
}

Pages/_Host.cshtml No arquivo , crie e instância do InitialApplicationState e passe-o


como um parâmetro para o aplicativo:

CSHTML

@using Microsoft.AspNetCore.Authentication

...

@{
var tokens = new InitialApplicationState
{
AccessToken = await HttpContext.GetTokenAsync("access_token"),
RefreshToken = await HttpContext.GetTokenAsync("refresh_token")
};
}

<component type="typeof(App)" param-InitialState="tokens"


render-mode="ServerPrerendered" />

App No componente ( App.razor ), resolva o serviço e inicialize-o com os dados do


parâmetro :

razor

@inject TokenProvider TokenProvider


...

@code {
[Parameter]
public InitialApplicationState? InitialState { get; set; }

protected override Task OnInitializedAsync()


{
TokenProvider.AccessToken = InitialState?.AccessToken;
TokenProvider.RefreshToken = InitialState?.RefreshToken;

return base.OnInitializedAsync();
}
}

Adicione uma referência de pacote ao aplicativo para o


Microsoft.AspNet.WebApi.Client pacote NuGet.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

No serviço que faz uma solicitação de API segura, injete o provedor de token e recupere
o token para a solicitação de API:

C#

using System;
using System.Net.Http;
using System.Threading.Tasks;

public class WeatherForecastService


{
private readonly HttpClient http;
private readonly TokenProvider tokenProvider;

public WeatherForecastService(IHttpClientFactory clientFactory,


TokenProvider tokenProvider)
{
http = clientFactory.CreateClient();
this.tokenProvider = tokenProvider;
}

public async Task<WeatherForecast[]> GetForecastAsync()


{
var token = tokenProvider.AccessToken;
var request = new HttpRequestMessage(HttpMethod.Get,
"https://localhost:5003/WeatherForecast");
request.Headers.Add("Authorization", $"Bearer {token}");
var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();

return await response.Content.ReadAsAsync<WeatherForecast[]>();


}
}

Definir o esquema de autenticação


Para um aplicativo que usa mais de um Middleware de Autenticação e, portanto, tem
mais de um esquema de autenticação, o esquema que Blazor usa pode ser definido
explicitamente na configuração do ponto de extremidade do Program.cs . O exemplo a
seguir define o esquema OIDC (OpenID Connect):

C#

using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapBlazorHub().RequireAuthorization(
new AuthorizeAttribute
{
AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
});
Proteger Blazor WebAssembly no
ASP.NET Core
Artigo • 21/12/2022 • 13 minutos para o fim da leitura

Aplicativos Blazor WebAssembly são protegidos da mesma maneira que SPAs


(aplicativos de página única). Há várias abordagens para autenticar usuários em SPAs,
mas a abordagem mais comum e abrangente é usar uma implementação baseada no
protocolo OAuth 2.0 , como o OIDC (OpenID Connect) .

Biblioteca de autenticação
Blazor WebAssembly dá suporte à autenticação e à autorização de aplicativos usando o
OIDC por meio da biblioteca
Microsoft.AspNetCore.Components.WebAssembly.Authentication . A biblioteca
fornece um conjunto de primitivos para autenticação direta em back-ends do ASP.NET
Core. A biblioteca integra o ASP.NET Core Identity ao suporte à autorização de API
baseado no Servidor Identity Duende . A biblioteca pode se autenticar em qualquer IP
(provedor de Identity de terceiros) com suporte para OIDC, que são chamados de OP
(Provedores do OpenID).

O suporte à autenticação no Blazor WebAssembly é criado sobre a Biblioteca de Clientes


OIDC ( oidc-client.js ), que é usada para lidar com os detalhes do protocolo de
autenticação subjacente.

Existem outras opções para autenticar SPAs, como o uso de cookies do SameSite. No
entanto, o design de engenharia do Blazor WebAssembly usa OAuth e OIDC como a
melhor opção para autenticação em Blazor WebAssembly aplicativos. A autenticação
baseada em token com base em JWTs (Tokens Web JSON) foi escolhida em vez da
autenticação baseada em cookie por motivos funcionais e de segurança:

O uso de um protocolo baseado em token proporciona uma área de superfície de


ataque menor, pois os tokens não são enviados em todas as solicitações.
Os pontos de extremidade do servidor não exigem proteção contra CSRF
(solicitação intersite forjada) porque os tokens são enviados explicitamente. Isso
permite que você hospede aplicativos Blazor WebAssembly ao lado de aplicativos
MVC ou Razor Pages.
Os tokens têm permissões mais estreitas que cookies. Por exemplo, tokens não
podem ser usados para gerenciar a conta de usuário ou alterar a senha de um
usuário a menos que essa funcionalidade seja implementada explicitamente.
Os tokens têm um tempo de vida curto, uma hora por padrão, o que limita a janela
de ataque. Os tokens também podem ser revogados a qualquer momento.
JWTs autocontidos oferecem garantias ao cliente e ao servidor sobre o processo
de autenticação. Por exemplo, um cliente tem meios para detectar e validar que os
tokens recebidos são legítimos e foram emitidos como parte de um determinado
processo de autenticação. Se um terceiro tentar trocar um token no meio do
processo de autenticação, o cliente poderá detectar o token trocado e evitar usá-
lo.
Tokens com OAuth e OIDC não dependem do agente de usuário se comportar
corretamente para garantir que o aplicativo esteja seguro.
Protocolos baseados em token, como OAuth e OIDC, permitem autenticar e
autorizar aplicativos hospedados e autônomos com o mesmo conjunto de
características de segurança.

) Importante

Para versões de ASP.NET Core que adotam o Duende Identity Server em Blazor
modelos de projeto, o Duende Software pode exigir que você pague uma taxa
de licença pelo uso de produção do Duende Identity Server. Para obter mais
informações, consulte Migrar do ASP.NET Core 5.0 para o 6.0.

Processo de autenticação com OIDC


A biblioteca Microsoft.AspNetCore.Components.WebAssembly.Authentication oferece
vários primitivos para implementar a autenticação e a autorização usando o OIDC. Em
termos gerais, a autenticação funciona da seguinte maneira:

Quando um usuário anônimo seleciona o botão de logon ou solicita uma página


com o atributo [Authorize] aplicado, o usuário é redirecionado para a página de
logon do aplicativo ( /authentication/login ).
Na página de logon, a biblioteca de autenticação se prepara para um
redirecionamento para o ponto de extremidade de autorização. O ponto de
extremidade de autorização está fora do aplicativo Blazor WebAssembly e pode
ser hospedado em uma origem separada. O ponto de extremidade é responsável
por determinar se o usuário é autenticado e por emitir um ou mais tokens em
resposta. A biblioteca de autenticação fornece um retorno de chamada de logon
para receber a resposta de autenticação.
Se o usuário não for autenticado, ele será redirecionado para o sistema de
autenticação subjacente, que geralmente é o ASP.NET Core Identity.
Se o usuário já estava autenticado, o ponto de extremidade de autorização gera
os tokens apropriados e redireciona o navegador para o ponto de extremidade
de retorno de chamada de logon ( /authentication/login-callback ).
Quando o aplicativo Blazor WebAssembly carrega o ponto de extremidade de
retorno de chamada de logon ( /authentication/login-callback ), a resposta de
autenticação é processada.
Se o processo de autenticação for concluído com êxito, o usuário será
autenticado e, opcionalmente, enviado para a URL protegida original solicitada
pelo usuário.
Se o processo de autenticação falhar por qualquer motivo, o usuário será
enviado para a página de falha de logon ( /authentication/login-failed ) e um
erro será exibido.

componente Authentication
O componente Authentication ( Pages/Authentication.razor ) trata operações de
autenticação remota e permite que o aplicativo:

Configure rotas de aplicativo para estados de autenticação.


Defina conteúdo da interface do usuário para estados de autenticação.
Gerencie o estado de autenticação.

Ações de autenticação, como registrar ou conectar um usuário, são passadas para o


componente RemoteAuthenticatorViewCore<TAuthenticationState> da estrutura Blazor,
que persiste e controla o estado em operações de autenticação.

Para obter mais informações e exemplos, consulte Cenários de segurança adicionais de


Blazor WebAssembly no ASP.NET Core.

Autorização
Em aplicativos Blazor WebAssembly, as verificações de autorização podem ser ignoradas
porque todos os códigos do lado do cliente podem ser modificados pelos usuários. Isso
também ocorre com todas as tecnologias de aplicativo do lado do cliente, incluindo
estruturas de SPA do JavaScript ou aplicativos nativos em qualquer sistema operacional.

Sempre execute as verificações de autorização no servidor em qualquer ponto de


extremidade da API acessada pelo aplicativo do lado do cliente.

Personalizar a autenticação
O Blazor WebAssembly fornece métodos para adicionar e recuperar parâmetros
adicionais para a biblioteca de autenticação subjacente realizar operações de
autenticação remota com provedores de identidade externos.

Para passar parâmetros adicionais, NavigationManager dá suporte à passagem e à


recuperação do estado de entrada do histórico ao executar alterações de localização
externas. Para saber mais, consulte os recursos a seguir:

BlazorFundamentos>Artigo de roteamento e navegação


Estado do histórico de navegação
Opções de navegação
Documentação do MDN: API de Histórico

O estado armazenado pela API de Histórico fornece os seguintes benefícios para a


autenticação remota:

O estado passado para o ponto de extremidade do aplicativo protegido está


vinculado à navegação executada para autenticar o usuário no ponto de
extremidade authentication/login .
O trabalho adicional de codificar e decodificar dados é evitado.
A área da superfície de ataque é reduzida. Ao contrário do uso da cadeia de
caracteres de consulta para armazenar o estado de navegação, uma navegação de
nível superior ou influência de uma origem diferente não pode definir o estado
armazenado pela API de Histórico.
A entrada de histórico é substituída após uma autenticação bem-sucedida,
portanto, o estado anexado à entrada de histórico é removido e não requer
limpeza.

O InteractiveRequestOptions representa a solicitação ao provedor de identidade para


fazer logon ou provisionar um token de acesso.

O NavigationManagerExtensions fornece o método NavigateToLogin para uma


operação de logon e NavigateToLogout para uma operação de logoff. Os métodos
chamam NavigationManager.NavigateTo, definindo o estado de entrada de histórico
com um InteractiveRequestOptions passado ou uma instância de
InteractiveRequestOptions criada pelo método para:

Um usuário se conectando (InteractionType.SignIn) com o URI atual da URL de


retorno.
Um usuário saindo (InteractionType.SignOut) com a URL de retorno.

Os seguintes cenários de autenticação são abordados no artigo Cenários de segurança


adicionais do ASP.NET Core Blazor WebAssembly:
Personalizar o processo de logon
Logoff com uma URL de retorno personalizada
Personalizar opções antes de obter um token de modo interativo
Personalizar opções ao usar um IAccessTokenProvider
Obter o caminho de logon das opções de autenticação

Exigir autorização para todo o aplicativo


Aplique o [Authorize] atributo (documentação da API) a cada Razor componente do
aplicativo usando uma das seguintes abordagens:

No arquivo Importações do aplicativo, adicione uma diretiva @using para o


namespace Microsoft.AspNetCore.Authorization com uma diretiva @attribute para
o atributo [Authorize].

_Imports.razor :

razor

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

Permitir acesso anônimo ao componente para permitir o Authentication


redirecionamento para o provedor de identidade. Adicione o código Razor a
seguir ao componente Authentication sob sua diretiva @page.

Pages/Authentication.razor :

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@attribute [AllowAnonymous]

Adicione o atributo a cada Razor componente na Pages pasta sob suas @page
diretivas:

razor

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

7 Observação
Não há suporte para a configuração de um AuthorizationOptions.FallbackPolicy
para uma política com RequireAuthenticatedUser.

Usar um registro de aplicativo do provedor de


identidade por aplicativo
Alguns dos artigos sob esta Visão geral pertencem a qualquer um dos seguintes Blazor
cenários de hospedagem que envolvem dois ou mais aplicativos:

Uma solução hospedadaBlazor WebAssembly, composta por dois aplicativos: um


aplicativo do lado Blazor WebAssembly do cliente e um aplicativo host do lado do
servidor ASP.NET Core. Usuários autenticados no aplicativo cliente acessam
recursos e dados do servidor fornecidos pelo aplicativo de servidor.
Um aplicativo autônomo Blazor WebAssembly que usa a API Web com usuários
autenticados para acessar recursos de servidor e dados fornecidos por um
aplicativo de servidor. Esse cenário é semelhante ao uso de uma solução
hospedada Blazor WebAssembly ; mas, nesse caso, o aplicativo cliente não é
hospedado pelo aplicativo do servidor.

Quando esses cenários são implementados em exemplos de documentação, dois


registros de provedor de identidade são usados, um para o aplicativo cliente e outro
para o aplicativo de servidor. O uso de registros separados, por exemplo, no Azure
Active Directory, não é estritamente necessário. No entanto, usar dois registros é uma
prática recomendada de segurança porque isola os registros por aplicativo. O uso de
registros separados também permite a configuração independente dos registros de
cliente e servidor.

Tokens de atualização
Embora os tokens de atualização não possam ser protegidos em Blazor WebAssembly
aplicativos, eles podem ser usados se você implementá-los com estratégias de
segurança apropriadas.

Para aplicativos autônomos Blazor WebAssembly no ASP.NET Core 6.0 ou posterior,


recomendamos usar:

O fluxo de código de autorização do OAuth 2.0 (código) com Chave de Prova para
Troca de Código (PKCE) .
Um token de atualização que tem uma expiração curta.
Um token de atualização girado .
Um token de atualização com uma expiração após o qual um novo fluxo de
autorização interativa é necessário para atualizar as credenciais do usuário.

Para soluções hospedadas Blazor WebAssembly , os tokens de atualização podem ser


mantidos e usados pelo aplicativo do lado do servidor para acessar APIs de terceiros.
Para obter mais informações, consulte Cenários de segurança adicionais de Blazor
WebAssembly no ASP.NET Core.

Para saber mais, consulte os recursos a seguir:

plataforma de identidade da Microsoft atualizar tokens: atualizar o tempo de vida


do token
OAuth 2.0 para aplicativos Browser-Based (especificação IETF)

Estabelecer declarações para usuários


Os aplicativos geralmente exigem declarações para usuários com base em uma
chamada à API Web para um servidor. Por exemplo, as declarações frequentemente são
usadas para estabelecer autorização em um aplicativo. Nesses cenários, o aplicativo
solicita um token de acesso para acessar o serviço e usa o token para obter dados do
usuário para criar declarações.

Para ver exemplos, consulte os seguintes recursos:

Cenários adicionais: personalizar o usuário


ASP.NET Core Blazor WebAssembly com grupos e funções do Azure Active
Directory

Suporte de pré-geração
Não há suporte para pré-renderização para pontos de extremidade de autenticação
(segmento de linha /authentication/ ).

Para obter mais informações, consulte Cenários de segurança adicionais de Blazor


WebAssembly no ASP.NET Core.

Serviço de Aplicativo do Azure no Linux com o


Identity Server
Especifique o emissor explicitamente ao implantar no Serviço de Aplicativo do Azure no
Linux com o Identity Server.
Para obter mais informações, consulte Introdução à autenticação para Aplicativos de
Página Única no ASP.NET Core.

Autenticação do Windows
Não recomendamos usar a Autenticação do Windows com Blazor Webassembly ou com
qualquer outra estrutura de SPA. É recomendável usar protocolos baseados em token
em vez da Autenticação do Windows, como o OIDC com o ADFS (Serviços de Federação
do Active Directory).

Se a Autenticação do Windows for usada com o Webassembly Blazor ou com qualquer


outra estrutura de SPA, medidas adicionais serão necessárias para proteger o aplicativo
contra tokens CSRF (solicitação intersite forjada). As mesmas preocupações que se
aplicam a cookies se aplicam à Autenticação do Windows com a adição de que a
Autenticação do Windows não oferece um mecanismo para impedir o
compartilhamento do contexto de autenticação entre origens. Os aplicativos que usam
a Autenticação do Windows sem proteção adicional do CSRF devem pelo menos ser
restritos à intranet de uma organização e não ser usados na Internet aberta.

Para obter mais informações, consulte Impedir ataques de XSRF/CSRF (solicitação


intersite forjada) no ASP.NET Core.

Assegure um hub SignalR


Para assegurar um hub SignalR:

Server No projeto, aplique o [Authorize] atributo à classe hub ou aos métodos da


classe hub.

No componente Client do projeto, forneça um token de acesso para a conexão do


Hub:

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject NavigationManager Navigation

...

var tokenResult = await TokenProvider.RequestAccessToken();

if (tokenResult.TryGetToken(out var token))


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"),
options => { options.AccessTokenProvider = () =>
Task.FromResult(token?.Value); })
.Build();

...
}

Para obter mais informações, confira Autenticação e autorização no ASP.NET


CoreSignalR.

Log
Esta seção se aplica a Blazor WebAssembly aplicativos no ASP.NET Core 7.0 ou posterior.

Para habilitar o log de depuração ou rastreamento, consulte a seção Log de autenticação


(Blazor WebAssembly) na versão 7.0 do artigo de registro em log do ASP.NET
CoreBlazor.

A área restrita do WebAssembly


A área restrita WebAssembly restringe o acesso ao ambiente do sistema que executa o
código WebAssembly, incluindo acesso a subsistemas de E/S, armazenamento e
recursos do sistema e ao sistema operacional. O isolamento entre o código
WebAssembly e o sistema que executa o código torna o WebAssembly uma estrutura
de codificação segura para sistemas. No entanto, o WebAssembly é vulnerável a ataques
de canal lateral no nível do hardware. As precauções normais e a devida diligência no
fornecimento de hardware e na colocação de limitações no acesso ao hardware se
aplicam.

O WebAssembly não é de propriedade ou é mantido por Microsoft.

Para obter mais informações, consulte os seguintes recursos do W3C:

WebAssembly: Segurança
Especificação de WebAssembly: Considerações de segurança
Grupo da Comunidade WebAssembly do W3C: comentários e problemas : o link
Grupo da Comunidade WebAssembly do W3C só é fornecido para referência,
deixando claro que as vulnerabilidades e bugs de segurança do WebAssembly são
corrigidos continuamente, geralmente relatados e abordados pelo navegador. Não
envie comentários ou relatórios de bugs Blazor para o Grupo da Comunidade
WebAssembly do W3C.Blazor os comentários devem ser relatados à unidade de
produto Microsoft ASP.NET Core . Se a unidade de produto Microsoft determinar
que existe um problema subjacente com o WebAssembly, a unidade do produto
tomará as etapas apropriadas para relatar o problema ao Grupo da Comunidade
WebAssembly do W3C.

Diretrizes de implementação
Os artigos nesta Visão geral fornecem informações sobre como autenticar usuários em
aplicativos Blazor WebAssembly em provedores específicos.

Aplicativos Blazor WebAssembly autônomos:

Diretrizes gerais para provedores de OIDC e a Biblioteca de Autenticação


WebAssembly
Contas Microsoft
AAD (Azure Active Directory)
Azure Active Directory (AAD) B2C

Aplicativos Blazor WebAssembly hospedados:

AAD (Azure Active Directory)


Azure Active Directory (AAD) B2C
Servidor Identity

Outras diretrizes de configuração são encontradas nos seguintes artigos:

Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly


Usar a API do Graph com o ASP.NET Core Blazor WebAssembly

Recursos adicionais
Documentação da plataforma de identidade da Microsoft
Documentação geral
Tokens de acesso
Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores
de carga
O uso de Middleware de Cabeçalhos Encaminhados para preservar informações
do esquema HTTPS entre servidores proxy e redes internas.
Cenários e casos de uso adicionais, incluindo configuração de esquema manual,
solicitação de alterações de caminho para roteamento de solicitação correto e
encaminhamento do esquema de solicitação para proxies reversos do Linux e
não IIS.
Pré-geração com autenticação
WebAssembly: Segurança
Especificação de WebAssembly: Considerações de segurança
Proteger um aplicativo autônomo
ASP.NET Core Blazor WebAssembly com
a biblioteca de autenticação
Artigo • 21/12/2022 • 57 minutos para o fim da leitura

Este artigo explica como proteger um aplicativo autônomo ASP.NET Core Blazor
WebAssembly com a Blazor WebAssembly biblioteca de Autenticação.

Para O AAD (Azure Active Directory) e AAD B2C (Azure Active Directory B2C), não siga as
diretrizes neste tópico. Consulte os tópicos AAD e AAD B2C neste nó de tabela de
conteúdo.

Para criar um aplicativo autônomo Blazor WebAssembly que usa


Microsoft.AspNetCore.Components.WebAssembly.Authentication a biblioteca, siga as
diretrizes para sua escolha de ferramentas. Se adicionar suporte para autenticação,
consulte as seções a seguir deste artigo para obter diretrizes sobre como configurar e
configurar o aplicativo.

7 Observação

O Identity Provedor (IP) deve usar o OIDC (OpenID Connect). Por exemplo, o IP
do Facebook não é um provedor compatível com OIDC, portanto, as diretrizes
neste tópico não funcionam com o IP do Facebook. Para obter mais informações,
consulte Secure ASP.NET Core Blazor WebAssembly.

Visual Studio

Para criar um novo Blazor WebAssembly projeto com um mecanismo de


autenticação:

1. Após escolher o modelo Aplicativo Blazor WebAssembly na caixa de diálogo


Criar um aplicativo Web ASP.NET Core, selecione Alterar em Autenticação.

2. Selecione Contas de Usuário Individuais para usar o sistema do Identity


ASP.NET Core. Essa seleção adiciona suporte à autenticação e não resulta no
armazenamento de usuários em um banco de dados. As seções a seguir deste
artigo fornecem mais detalhes.
Pacote de autenticação
Quando um aplicativo é criado para usar contas de usuário individuais, o aplicativo
recebe automaticamente uma referência de pacote para o
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote. O pacote
fornece um conjunto de primitivos que ajudam o aplicativo a autenticar usuários e obter
tokens para chamar APIs protegidas.

Se adicionar autenticação a um aplicativo, adicione manualmente o


Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote ao
aplicativo.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Suporte ao serviço de autenticação


O suporte para autenticação de usuários é registrado no contêiner de serviço com o
AddOidcAuthentication método de extensão fornecido pelo
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote. Esse
método configura os serviços necessários para que o aplicativo interaja com o Identity
Provedor (IP).

Para um novo aplicativo, forneça valores para os {AUTHORITY} espaços reservados e


{CLIENT ID} na configuração a seguir. Forneça outros valores de configuração

necessários para uso com o IP do aplicativo. O exemplo é para o Google, que requer
PostLogoutRedirectUri , RedirectUri e ResponseType . Se adicionar autenticação a um

aplicativo, adicione manualmente o código e a configuração a seguir ao aplicativo com


valores para os espaços reservados e outros valores de configuração.

Program.cs :

C#

builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Local", options.ProviderOptions);
});
A configuração é fornecida pelo wwwroot/appsettings.json arquivo:

JSON

{
"Local": {
"Authority": "{AUTHORITY}",
"ClientId": "{CLIENT ID}"
}
}

Exemplo de OIDC do Google OAuth 2.0 para um aplicativo que é executado no


endereço localhost na porta 5001:

JSON

{
"Local": {
"Authority": "https://accounts.google.com/",
"ClientId": "2.......7-
e.....................q.apps.googleusercontent.com",
"PostLogoutRedirectUri": "https://localhost:5001/authentication/logout-
callback",
"RedirectUri": "https://localhost:5001/authentication/login-callback",
"ResponseType": "id_token"
}
}

O URI de redirecionamento ( https://localhost:5001/authentication/login-callback ) é


registrado no console de APIs do Google emURIs de redirecionamento autorizado
de credenciais> {NAME} >, em que {NAME} é o nome do cliente do aplicativo na lista de
aplicativos IDs do cliente OAuth 2.0 do console de APIs do Google.

O suporte à autenticação para aplicativos autônomos é oferecido usando o OIDC


(OpenID Connect). O AddOidcAuthentication método aceita um retorno de chamada
para configurar os parâmetros necessários para autenticar um aplicativo usando o OIDC.
Os valores necessários para configurar o aplicativo podem ser obtidos do IP compatível
com OIDC. Obtenha os valores ao registrar o aplicativo, que normalmente ocorre em
seu portal online.

Escopos de token de acesso


O Blazor WebAssembly modelo configura automaticamente os escopos padrão para
openid e profile .
O Blazor WebAssembly modelo não configura automaticamente o aplicativo para
solicitar um token de acesso para uma API segura. Para provisionar um token de acesso
como parte do fluxo de entrada, adicione o escopo aos escopos de token padrão do
OidcProviderOptions. Se adicionar autenticação a um aplicativo, adicione manualmente
o código a seguir e configure o URI de escopo.

Program.cs :

C#

builder.Services.AddOidcAuthentication(options =>
{
...
options.ProviderOptions.DefaultScopes.Add("{SCOPE URI}");
});

Para obter mais informações, consulte as seções a seguir do artigo Cenários adicionais :

Solicitar tokens de acesso adicionais


Anexar tokens a solicitações de saída

Importa o arquivo
O Microsoft.AspNetCore.Components.Authorization namespace é disponibilizado em
todo o aplicativo por meio do _Imports.razor arquivo:

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Página de índice
A página Índice ( wwwroot/index.html ) inclui um script que define o
AuthenticationService em JavaScript. AuthenticationService manipula os detalhes de
baixo nível do protocolo OIDC. O aplicativo chama internamente métodos definidos no
script para executar as operações de autenticação.

HTML

<script
src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/Aut
henticationService.js"></script>

Componente do aplicativo
O App componente ( App.razor ) é semelhante ao App componente encontrado em
Blazor Server aplicativos:

O CascadingAuthenticationState componente gerencia a exposição do


AuthenticationState ao restante do aplicativo.
O AuthorizeRouteView componente garante que o usuário atual esteja autorizado
a acessar uma determinada página ou renderize o RedirectToLogin componente.
O RedirectToLogin componente gerencia o redirecionamento de usuários não
autorizados para a página de logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


App componente ( App.razor ) não é mostrada nesta seção. Para inspecionar a marcação

do componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o App componente ( App.razor ) no aplicativo gerado.

Inspecione o App componente ( App.razor ) na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .
Componente RedirectToLogin
O RedirectToLogin componente ( Shared/RedirectToLogin.razor ):

Gerencia o redirecionamento de usuários não autorizados para a página de logon.


Preserva a URL atual que o usuário está tentando acessar para que ele possa ser
retornado a essa página se a autenticação for bem-sucedida.

razor

@inject NavigationManager Navigation


@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions
>> Options
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin(Options.Get(
Microsoft.Extensions.Options.Options.DefaultName)
.AuthenticationPaths.LogInPath);
}
}

Componente LoginDisplay
O LoginDisplay componente ( Shared/LoginDisplay.razor ) é renderizado no MainLayout
componente ( Shared/MainLayout.razor ) e gerencia os seguintes comportamentos:

Para usuários autenticados:


Exibe o nome de usuário atual.
Oferece um botão para sair do aplicativo.
Para usuários anônimos, oferece a opção de fazer logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação do


LoginDisplay componente não é mostrada nesta seção. Para inspecionar a marcação do

componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o LoginDisplay componente no aplicativo gerado.

Inspecione o LoginDisplay componente na origem de referência .


7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente de autenticação
A página produzida pelo Authentication componente ( Pages/Authentication.razor )
define as rotas necessárias para lidar com diferentes estágios de autenticação.

O RemoteAuthenticatorView componente:

É fornecido pelo
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote.
Gerencia a execução das ações apropriadas em cada estágio de autenticação.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code {
[Parameter]
public string Action { get; set; }
}

Solucionar problemas

Log
Para habilitar o log de depuração ou rastreamento para Blazor WebAssembly
autenticação no ASP.NET Core 7.0 ou posterior, consulte ASP.NET Core Blazor registro
em log.

Erros comuns
Configuração incorreta do aplicativo ou Identity provedor (IP)

Os erros mais comuns são causados pela configuração incorreta. A seguir, estão
alguns exemplos:
Dependendo dos requisitos do cenário, uma Autoridade ausente ou incorreta,
Instância, ID do Locatário, Domínio do Locatário, ID do Cliente ou URI de
Redirecionamento impede que um aplicativo autentique clientes.
Um escopo de token de acesso incorreto impede que os clientes acessem
pontos de extremidade da API Web do servidor.
Permissões de API de servidor incorretas ou ausentes impedem que os clientes
acessem pontos de extremidade da API Web do servidor.
Executar o aplicativo em uma porta diferente do configurado no URI de
Redirecionamento do Identity registro de aplicativo do Provedor.

As seções de configuração das diretrizes deste artigo mostram exemplos da


configuração correta. Verifique cuidadosamente cada seção do artigo em busca de
configuração incorreta de aplicativo e IP.

Se a configuração aparecer correta:

Analisar logs de aplicativo.

Examine o tráfego de rede entre o aplicativo cliente e o aplicativo IP ou servidor


com as ferramentas de desenvolvedor do navegador. Muitas vezes, uma
mensagem de erro exata ou uma mensagem com uma pista do que está
causando o problema é retornada ao cliente pelo aplicativo IP ou servidor
depois de fazer uma solicitação. Ferramentas de desenvolvedor diretrizes são
encontradas nos seguintes artigos:
Google Chrome (documentação do Google)
Microsoft Edge
Mozilla Firefox (documentação do Mozilla)

Decodificar o conteúdo de um JSToken Web ON (JWT) usado para autenticar


um cliente ou acessar uma API Web do servidor, dependendo de onde o
problema está ocorrendo. Para obter mais informações, consulte Inspecionar o
conteúdo de um JSToken Web ON (JWT).

A equipe de documentação responde a comentários de documentos e bugs em


artigos (abra um problema na seção Comentários desta página ), mas não
consegue fornecer suporte ao produto. Vários fóruns de suporte público estão
disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos
o uso do seguinte:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Os fóruns anteriores não são de propriedade ou controlados por Microsoft.

Para relatórios de bugs de estrutura reproduzível não confidenciais, não


confidenciais e não confidenciais, abra um problema com a unidade do produto
ASP.NET Core . Não abra um problema com a unidade do produto até que você
investigue minuciosamente a causa de um problema e não possa resolvê-lo por
conta própria e com a ajuda da comunidade em um fórum de suporte público. A
unidade do produto não é capaz de solucionar problemas de aplicativos
individuais que estão quebrados devido a simples configuração incorreta ou casos
de uso envolvendo serviços de terceiros. Se um relatório for confidencial ou
confidencial por natureza ou descrever uma possível falha de segurança no
produto que os invasores podem explorar, consulte Relatando problemas de
segurança e bugs (repositório GitHub dotnet/aspnetcore) .

Cliente não autorizado para o AAD

info: Microsoft. Falha na autorização de


AspNetCore.Authorization.DefaultAuthorizationService[2]. Esses requisitos não
foram atendidos: DenyAnonymousAuthorizationRequirement: requer um
usuário autenticado.

Erro de retorno de chamada de logon do AAD:


Erro: unauthorized_client
Descrição: AADB2C90058: The provided application is not configured to allow
public clients.

Para resolver o erro:

1. No portal do Azure, acesse o manifesto do aplicativo.


2. Defina o allowPublicClient atributo como null ou true .

Cookies e dados do site


Cookies e dados do site podem persistir entre as atualizações do aplicativo e interferir
no teste e na solução de problemas. Desmarque o seguinte ao fazer alterações no
código do aplicativo, alterações na conta de usuário com o provedor ou alterações na
configuração do aplicativo do provedor:

Entradas do cookieusuário
Aplicativos cookie
Dados do site armazenados e armazenados em cache

Uma abordagem para impedir que os dados persistentes cookiedo site e do site
interfiram no teste e na solução de problemas é:

Configurar um navegador
Use um navegador para testar que você pode configurar para excluir todos os
cookie dados do site e sempre que o navegador for fechado.
Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer
alteração no aplicativo, usuário de teste ou configuração do provedor.
Use um comando personalizado para abrir um navegador no modo anônimo ou
privado no Visual Studio:
Abra a caixa de diálogo Procurar com no botão Executar do Visual Studio.
Selecione o botão Adicionar.
Forneça o caminho para o navegador no campo Programa . Os seguintes
caminhos executáveis são locais de instalação típicos para Windows 10. Se o
navegador estiver instalado em um local diferente ou você não estiver usando
Windows 10, forneça o caminho para o executável do navegador.
Microsoft Edge: C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe
Google Chrome: C:\Program Files
(x86)\Google\Chrome\Application\chrome.exe
Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
No campo Argumentos , forneça a opção de linha de comando que o
navegador usa para abrir no modo anônimo ou privado. Alguns navegadores
exigem a URL do aplicativo.
Microsoft Edge: use -inprivate .
Google Chrome: use --incognito --new-window {URL} , em que o espaço
reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Mozilla Firefox: use -private -url {URL} , em que o espaço reservado {URL}
é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Forneça um nome no campo Nome amigável . Por exemplo, Firefox Auth
Testing .

Selecione o botão OK.


Para evitar a necessidade de selecionar o perfil do navegador para cada iteração
de teste com um aplicativo, defina o perfil como o padrão com o botão Definir
como Padrão .
Verifique se o navegador está fechado pelo IDE para qualquer alteração na
configuração do aplicativo, do usuário de teste ou do provedor.
Atualizações de aplicativos
Um aplicativo funcional pode falhar imediatamente após atualizar o SDK do .NET Core
no computador de desenvolvimento ou alterar as versões do pacote dentro do
aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao
executar atualizações principais. A maioria desses problemas pode ser corrigida
seguindo estas instruções:

1. Limpe os caches de pacote NuGet do sistema local executando dotnet nuget locals
all --clear de um shell de comando.
2. Exclua as pastas e obj do bin projeto.
3. Restaure e recompile o projeto.
4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar
o aplicativo.

7 Observação

Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de


destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do
NuGet ou o Gerenciador de Pacotes FuGet .

Executar o aplicativo Servidor


Ao testar e solucionar problemas de uma solução hospedadaBlazor WebAssembly,
verifique se você está executando o aplicativo no Server projeto. Por exemplo, no Visual
Studio, confirme se o projeto servidor está realçado em Gerenciador de Soluções antes
de iniciar o aplicativo com qualquer uma das seguintes abordagens:

Selecione o botão Executar.


Use Depurar>Iniciar Depuração no menu.
Pressione F5 .

Inspecionar o usuário
Os ativos de teste da estrutura ASP.NET Core incluem um Blazor WebAssembly
aplicativo cliente com um User componente que pode ser útil na solução de
problemas. O User componente pode ser usado diretamente em aplicativos ou servir
como base para personalização adicional:

User componente de teste no dotnet/aspnetcore repositório GitHub


7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Inspecionar o conteúdo de um JSToken Web ON (JWT)


Para decodificar um JSToken Web ON (JWT), use a ferramenta de jwt.ms do Microsoft .
Os valores na interface do usuário nunca saem do navegador.

Exemplo de JWT codificado (abreviado para exibição):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j ...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q

Exemplo de JWT decodificado pela ferramenta para um aplicativo que se autentica no


Azure AAD B2C:

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]
Recursos adicionais
Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly
Solicitações de API Web não autenticadas ou não autorizadas em um aplicativo
com um cliente padrão seguro
Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores
de carga: inclui diretrizes sobre:
O uso de Middleware de Cabeçalhos Encaminhados para preservar informações
do esquema HTTPS entre servidores proxy e redes internas.
Cenários e casos de uso adicionais, incluindo configuração de esquema manual,
solicitação de alterações de caminho para roteamento de solicitação correto e
encaminhamento do esquema de solicitação para proxies reversos do Linux e
não IIS.
Proteger um aplicativo autônomo
ASP.NET Core Blazor WebAssembly com
contas Microsoft
Artigo • 21/12/2022 • 56 minutos para o fim da leitura

Este artigo explica como criar um aplicativo autônomo Blazor WebAssembly que usa
contas Microsoft com o AAD (Azure Active Directory) para autenticação.

Registrar um aplicativo do AAD:

1. Navegue até Azure Active Directory no portal do Azure. Selecione Registros de


aplicativo na barra lateral. Selecione o botão Novo registro .
2. Forneça um Nome para o aplicativo (por exemplo, Blazor Contas Microsoft do
AAD Autônomo).
3. Em Tipos de contas compatíveis, escolha Contas em qualquer diretório
organizacional.
4. Defina a lista suspensa URI de redirecionamento como SPA (aplicativo de página
única) e forneça o seguinte URI de redirecionamento:
https://localhost/authentication/login-callback . Se você souber o URI de

redirecionamento de produção para o host padrão do Azure (por exemplo,


azurewebsites.net ) ou o host de domínio personalizado (por exemplo,
contoso.com ), também poderá adicionar o URI de redirecionamento de produção

ao mesmo tempo em que estiver fornecendo o localhost URI de


redirecionamento. Certifique-se de incluir o número da porta para :443 portas não
em quaisquer URIs de redirecionamento de produção que você adicionar.
5. Se você estiver usando um domínio de editor não verificado, desmarque a caixa de
seleção Permissões>Conceder consentimento do administrador para permissões
openid e offline_access . Se o domínio do editor for verificado, essa caixa de
seleção não estará presente.
6. Selecione Registrar.

7 Observação

Não é necessário fornecer o número da porta para um localhost URI de


redirecionamento do AAD. Para obter mais informações, consulte Restrições e
limitações do URI de redirecionamento (URL de resposta): exceções de localhost
(documentação do Azure).
Registre a ID do aplicativo (cliente) (por exemplo, 41451fa7-82d9-4673-8fa5-
69eff5a761fd ).

EmSpa (aplicativo de página única) deconfigurações> da Plataforma de


Autenticação>:

1. Confirme se o URI de Redirecionamento de


https://localhost/authentication/login-callback está presente.

2. Na seção Concessão implícita , verifique se as caixas de seleção tokens de acesso


e tokens de IDnão estão selecionadas.
3. Os padrões restantes para o aplicativo são aceitáveis para essa experiência.
4. Selecione o botão Salvar.

Criar o aplicativo. Substitua os espaços reservados no comando a seguir pelas


informações registradas anteriormente e execute o seguinte comando em um shell de
comando:

CLI do .NET

dotnet new blazorwasm -au SingleOrg --client-id "{CLIENT ID}" --tenant-id


"common" -o {APP NAME}

Espaço reservado Nome do portal do Azure Exemplo

{APP NAME} — BlazorSample

{CLIENT ID} ID do aplicativo (cliente) 41451fa7-82d9-4673-8fa5-69eff5a761fd

A localização de saída especificada com a opção -o|--output criará uma pasta de


projeto se ela não existir e se tornará parte do nome do aplicativo.

Adicione um par de MsalProviderOptions para openid e


offline_access DefaultAccessTokenScopes:

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
});

Depois de criar o aplicativo, você deve ser capaz de:


Faça logon no aplicativo usando uma conta Microsoft.
Solicitar tokens de acesso para APIs Microsoft. Para obter mais informações,
consulte:
Escopos de token de acesso
Início Rápido: configurar um aplicativo para expor APIs Web.

Pacote de autenticação
Quando um aplicativo é criado para usar contas corporativas ou de estudante
( SingleOrg ), o aplicativo recebe automaticamente uma referência de pacote para a
Biblioteca de Autenticação do Microsoft
(Microsoft.Authentication.WebAssembly.Msal ). O pacote fornece um conjunto de
primitivos que ajudam o aplicativo a autenticar usuários e obter tokens para chamar
APIs protegidas.

Se adicionar autenticação a um aplicativo, adicione manualmente o


Microsoft.Authentication.WebAssembly.Msal pacote ao aplicativo.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

O Microsoft.Authentication.WebAssembly.Msal pacote adiciona transitivamente o


Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote ao
aplicativo.

Suporte ao serviço de autenticação


O suporte para autenticação de usuários é registrado no contêiner de serviço com o
AddMsalAuthentication método de extensão fornecido pelo
Microsoft.Authentication.WebAssembly.Msal pacote. Esse método configura todos os
serviços necessários para que o aplicativo interaja com o Identity PROVEDOR (IP).

Program.cs :

C#
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});

O AddMsalAuthentication método aceita um retorno de chamada para configurar os


parâmetros necessários para autenticar um aplicativo. Os valores necessários para
configurar o aplicativo podem ser obtidos na configuração do AAD quando você
registra o aplicativo.

A configuração é fornecida pelo wwwroot/appsettings.json arquivo :

JSON

{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/common",
"ClientId": "{CLIENT ID}",
"ValidateAuthority": true
}
}

Exemplo:

JSON

{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/common",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"ValidateAuthority": true
}
}

Escopos de token de acesso


O Blazor WebAssembly modelo não configura automaticamente o aplicativo para
solicitar um token de acesso para uma API segura. Para provisionar um token de acesso
como parte do fluxo de entrada, adicione o escopo aos escopos de token de acesso
padrão do MsalProviderOptions:

C#
builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Especifique escopos adicionais com AdditionalScopesToConsent :

C#

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");

Para obter mais informações, consulte as seguintes seções do artigo Cenários adicionais
:

Solicitar tokens de acesso adicionais


Anexar tokens a solicitações de saída

Modo de logon
A estrutura usa como padrão o modo de logon pop-up e volta para o modo de logon
de redirecionamento se um pop-up não puder ser aberto. Configure a MSAL para usar o
modo de logon de redirecionamento definindo a LoginMode propriedade de
MsalProviderOptions como redirect :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.LoginMode = "redirect";
});

A configuração padrão é popup e o valor da cadeia de caracteres não diferencia


maiúsculas de minúsculas.

Importa o arquivo
O Microsoft.AspNetCore.Components.Authorization namespace é disponibilizado em
todo o aplicativo por meio do _Imports.razor arquivo :

razor
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Página de índice
A página Índice ( wwwroot/index.html ) inclui um script que define o
AuthenticationService em JavaScript. AuthenticationService manipula os detalhes de

baixo nível do protocolo OIDC. O aplicativo chama internamente métodos definidos no


script para executar as operações de autenticação.

HTML

<script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>

Componente do aplicativo
O App componente ( App.razor ) é semelhante ao App componente encontrado em
Blazor Server aplicativos:

O CascadingAuthenticationState componente gerencia a exposição do


AuthenticationState ao restante do aplicativo.
O AuthorizeRouteView componente garante que o usuário atual esteja autorizado
a acessar uma determinada página ou renderize o RedirectToLogin componente
de outra forma.
O RedirectToLogin componente gerencia o redirecionamento de usuários não
autorizados para a página de logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


App componente ( App.razor ) não é mostrada nesta seção. Para inspecionar a marcação
do componente para uma determinada versão, use uma das seguintes abordagens:
Crie um aplicativo provisionado para autenticação do modelo de projeto padrão
Blazor WebAssembly para a versão do ASP.NET Core que você pretende usar.
Inspecione o App componente ( App.razor ) no aplicativo gerado.

Inspecione o App componente ( App.razor ) na fonte de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente RedirectToLogin
O RedirectToLogin componente ( Shared/RedirectToLogin.razor ):

Gerencia o redirecionamento de usuários não autorizados para a página de logon.


Preserva a URL atual que o usuário está tentando acessar para que ele possa ser
retornado a essa página se a autenticação for bem-sucedida.

razor

@inject NavigationManager Navigation


@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions
>> Options
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin(Options.Get(
Microsoft.Extensions.Options.Options.DefaultName)
.AuthenticationPaths.LogInPath);
}
}

Componente LoginDisplay
O LoginDisplay componente ( Shared/LoginDisplay.razor ) é renderizado no MainLayout
componente ( Shared/MainLayout.razor ) e gerencia os seguintes comportamentos:

Para usuários autenticados:


Exibe o nome de usuário atual.
Oferece um botão para fazer logoff do aplicativo.
Para usuários anônimos, oferece a opção de fazer logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


LoginDisplay componente não é mostrada nesta seção. Para inspecionar a marcação do

componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão do ASP.NET Core que você pretende usar.
Inspecione o LoginDisplay componente no aplicativo gerado.

Inspecione o LoginDisplay componente na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente de autenticação
A página produzida pelo Authentication componente ( Pages/Authentication.razor )
define as rotas necessárias para lidar com diferentes estágios de autenticação.

O RemoteAuthenticatorView componente:

É fornecido pelo
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote.
Gerencia a execução das ações apropriadas em cada estágio de autenticação.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />

@code {
[Parameter]
public string Action { get; set; }
}

Solucionar problemas

Log
Para habilitar o log de depuração ou rastreamento para Blazor WebAssembly
autenticação no ASP.NET Core 7.0 ou posterior, consulte ASP.NET Core Blazor registro
em log.

Erros comuns
Configuração incorreta do aplicativo ou Identity provedor (IP)

Os erros mais comuns são causados pela configuração incorreta. A seguir, estão
alguns exemplos:
Dependendo dos requisitos do cenário, uma Autoridade, Instância, ID do
Locatário, Domínio do Locatário, ID do Cliente ou URI de Redirecionamento
ausente ou incorreto impede que um aplicativo autentique clientes.
Um escopo de token de acesso incorreto impede que os clientes acessem
pontos de extremidade da API Web do servidor.
Permissões incorretas ou ausentes da API do servidor impedem que os clientes
acessem pontos de extremidade da API Web do servidor.
A execução do aplicativo em uma porta diferente da está configurada no URI de
Redirecionamento do Identity registro de aplicativo do Provedor.

As seções de configuração das diretrizes deste artigo mostram exemplos da


configuração correta. Verifique cuidadosamente cada seção do artigo em busca de
configuração incorreta de aplicativo e IP.

Se a configuração parecer correta:

Analisar logs do aplicativo.

Examine o tráfego de rede entre o aplicativo cliente e o aplicativo IP ou servidor


com as ferramentas de desenvolvedor do navegador. Geralmente, uma
mensagem de erro exata ou uma mensagem com uma pista do que está
causando o problema é retornada ao cliente pelo aplicativo IP ou servidor
depois de fazer uma solicitação. Ferramentas de desenvolvedor diretrizes são
encontradas nos seguintes artigos:
Google Chrome (documentação do Google)
Microsoft Edge
Mozilla Firefox (documentação do Mozilla)

Decodificar o conteúdo de um JSToken Web ON (JWT) usado para autenticar


um cliente ou acessar uma API Web do servidor, dependendo de onde o
problema está ocorrendo. Para obter mais informações, consulte Inspecionar o
conteúdo de um JSToken Web ON (JWT).

A equipe de documentação responde a comentários de documentos e bugs em


artigos (abra um problema na seção Comentários desta página ), mas não
consegue fornecer suporte ao produto. Vários fóruns de suporte público estão
disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos
o uso do seguinte:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Os fóruns anteriores não são de propriedade ou controlados por Microsoft.

Para relatórios de bugs de estrutura não confidenciais, não confidenciais e não


confidenciais, abra um problema com a unidade de produto ASP.NET Core . Não
abra um problema com a unidade de produto até que você tenha investigado
minuciosamente a causa de um problema e não possa resolvê-lo por conta própria
e com a ajuda da comunidade em um fórum de suporte público. A unidade de
produto não é capaz de solucionar problemas de aplicativos individuais que foram
interrompidos devido a simples erros de configuração ou casos de uso envolvendo
serviços de terceiros. Se um relatório for confidencial ou confidencial por natureza
ou descrever uma possível falha de segurança no produto que os invasores podem
explorar, consulte Relatando problemas de segurança e bugs (repositório GitHub
dotnet/aspnetcore) .

Cliente não autorizado para o AAD

info: Microsoft. Falha na autorização de


AspNetCore.Authorization.DefaultAuthorizationService[2]. Esses requisitos não
foram atendidos: DenyAnonymousAuthorizationRequirement: requer um
usuário autenticado.
Erro de retorno de chamada de logon do AAD:
Erro: unauthorized_client
Descrição: AADB2C90058: The provided application is not configured to allow
public clients.

Para resolver o erro:

1. No portal do Azure, acesse o manifesto do aplicativo.


2. Defina o allowPublicClient atributo como null ou true .

Cookies e dados do site


Cookieos dados do site e do s podem persistir entre atualizações de aplicativo e
interferir no teste e na solução de problemas. Desmarque o seguinte ao fazer alterações
no código do aplicativo, alterações na conta de usuário com o provedor ou alterações
na configuração do aplicativo do provedor:

Entradas do cookieusuário
Aplicativos cookies
Dados do site armazenados e armazenados em cache

Uma abordagem para impedir que os dados persistentes cookiede s e site interfiram em
testes e solução de problemas é:

Configurar um navegador
Use um navegador para testar que você pode configurar para excluir todos os
cookie dados do site e sempre que o navegador for fechado.
Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer
alteração no aplicativo, usuário de teste ou configuração do provedor.
Use um comando personalizado para abrir um navegador no modo anônimo ou
privado no Visual Studio:
Abra a caixa de diálogo Procurar com no botão Executar do Visual Studio.
Selecione o botão Adicionar.
Forneça o caminho para o navegador no campo Programa . Os caminhos
executáveis a seguir são locais de instalação típicos para Windows 10. Se o
navegador estiver instalado em um local diferente ou você não estiver usando
Windows 10, forneça o caminho para o executável do navegador.
Microsoft Edge: C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe
Google Chrome: C:\Program Files
(x86)\Google\Chrome\Application\chrome.exe
Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
No campo Argumentos , forneça a opção de linha de comando que o
navegador usa para abrir no modo anônimo ou privado. Alguns navegadores
exigem a URL do aplicativo.
Microsoft Edge: use -inprivate .
Google Chrome: use --incognito --new-window {URL} , em que o espaço
reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Mozilla Firefox: use -private -url {URL} , em que o espaço reservado {URL}
é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Forneça um nome no campo Nome amigável . Por exemplo, Firefox Auth
Testing .

Selecione o botão OK.


Para evitar a necessidade de selecionar o perfil do navegador para cada iteração
de teste com um aplicativo, defina o perfil como o padrão com o botão Definir
como Padrão .
Verifique se o navegador está fechado pelo IDE para qualquer alteração no
aplicativo, usuário de teste ou configuração do provedor.

Atualizações de aplicativos
Um aplicativo funcional pode falhar imediatamente após atualizar o SDK do .NET Core
no computador de desenvolvimento ou alterar as versões do pacote dentro do
aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao
executar atualizações principais. A maioria desses problemas pode ser corrigida
seguindo estas instruções:

1. Limpe os caches de pacote NuGet do sistema local executando dotnet nuget locals
all --clear de um shell de comando.
2. Exclua as pastas e obj do bin projeto.
3. Restaure e recompile o projeto.
4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar
o aplicativo.

7 Observação

Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de


destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do
NuGet ou o Gerenciador de Pacotes FuGet .

Executar o aplicativo Servidor


Ao testar e solucionar problemas de uma solução hospedadaBlazor WebAssembly,
verifique se você está executando o aplicativo do Server projeto. Por exemplo, no Visual
Studio, confirme se o projeto Do servidor está realçado em Gerenciador de Soluções
antes de iniciar o aplicativo com qualquer uma das seguintes abordagens:

Selecione o botão Executar.


Use Depurar>Iniciar Depuração no menu.
Pressione F5 .

Inspecionar o usuário
Os ativos de teste da estrutura ASP.NET Core incluem um Blazor WebAssembly
aplicativo cliente com um User componente que pode ser útil na solução de
problemas. O User componente pode ser usado diretamente em aplicativos ou servir
como base para personalização adicional:

User componente de teste no dotnet/aspnetcore repositório GitHub

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Inspecionar o conteúdo de um JSToken Web ON (JWT)


Para decodificar um JSToken Web ON (JWT), use a ferramenta de jwt.ms do Microsoft .
Os valores na interface do usuário nunca saem do navegador.

Exemplo de JWT codificado (abreviado para exibição):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j ...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q
Exemplo de JWT decodificado pela ferramenta para um aplicativo que se autentica no
Azure AAD B2C:

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Recursos adicionais
Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly
Criar uma versão personalizada da biblioteca JavaScript Authentication.MSAL
Solicitações de API Web não autenticadas ou não autorizadas em um aplicativo
com um cliente padrão seguro
ASP.NET Core Blazor WebAssembly com grupos e funções do Azure Active
Directory
Início Rápido: Registrar um aplicativo na plataforma de identidade da Microsoft
Início Rápido: Configurar um aplicativo para expor APIs Web
Proteger um aplicativo autônomo
ASP.NET Core Blazor WebAssembly com
o Azure Active Directory
Artigo • 21/12/2022 • 57 minutos para o fim da leitura

Este artigo explica como criar um aplicativo autônomo Blazor WebAssembly que usa o
AAD (Azure Active Directory) para autenticação.

Registrar um aplicativo do AAD:

1. Navegue até Azure Active Directory no portal do Azure. Selecione Registros de


aplicativo na barra lateral. Selecione o botão Novo registro .
2. Forneça um Nome para o aplicativo (por exemplo, Blazor AAD autônomo).
3. Escolha um tipo de conta com suporte. Você pode selecionar Contas neste
diretório organizacional somente para essa experiência.
4. Defina a lista suspensa URI de redirecionamento como SPA (aplicativo de página
única) e forneça o seguinte URI de redirecionamento:
https://localhost/authentication/login-callback . Se você souber o URI de
redirecionamento de produção para o host padrão do Azure (por exemplo,
azurewebsites.net ) ou o host de domínio personalizado (por exemplo,

contoso.com ), também poderá adicionar o URI de redirecionamento de produção


ao mesmo tempo em que estiver fornecendo o localhost URI de
redirecionamento. Certifique-se de incluir o número da porta para portas não :443
em qualquer URIs de redirecionamento de produção que você adicionar.
5. Se você estiver usando um domínio publicador não verificado, desmarque a caixa
de seleção Permissões>Conceder consentimento do administrador para
permissões openid e offline_access . Se o domínio do editor for verificado, essa
caixa de seleção não estará presente.
6. Selecione Registrar.

7 Observação

Não é necessário fornecer o número da porta para um localhost URI de


redirecionamento do AAD. Para obter mais informações, consulte Restrições e
limitações do URI de redirecionamento (URL de resposta): exceções de localhost
(documentação do Azure).

Registre as seguintes informações:


ID do aplicativo (cliente) (por exemplo, 41451fa7-82d9-4673-8fa5-69eff5a761fd )
ID do diretório (locatário) (por exemplo, e86c78e2-8bb4-4c41-aefd-918e0565a45e )

EmConfigurações> da Plataforma de Autenticação>Aplicativo de página única (SPA):

1. Confirme se o URI de Redirecionamento de


https://localhost/authentication/login-callback está presente.

2. Na seção Concessão implícita , verifique se as caixas de seleção para tokens de


acesso e tokens de IDnão estão selecionadas.
3. Os padrões restantes para o aplicativo são aceitáveis para essa experiência.
4. Selecione o botão Salvar.

Crie o aplicativo em uma pasta vazia. Substitua os espaços reservados no comando a


seguir pelas informações registradas anteriormente e execute o comando em um shell
de comando:

CLI do .NET

dotnet new blazorwasm -au SingleOrg --client-id "{CLIENT ID}" -o {APP NAME}
--tenant-id "{TENANT ID}"

Espaço reservado Nome do portal do Azure Exemplo

{APP NAME} — BlazorSample

{CLIENT ID} ID do aplicativo (cliente) 41451fa7-82d9-4673-8fa5-69eff5a761fd

{TENANT ID} ID do diretório (locatário) e86c78e2-8bb4-4c41-aefd-918e0565a45e

A localização de saída especificada com a opção -o|--output criará uma pasta de


projeto se ela não existir e se tornará parte do nome do aplicativo.

Adicione um MsalProviderOptions para User.Read permissão com


DefaultAccessTokenScopes:

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes
.Add("https://graph.microsoft.com/User.Read");
});

Depois de criar o aplicativo, você deve ser capaz de:


Faça logon no aplicativo usando uma conta de usuário do AAD.
Solicitar tokens de acesso para APIs de Microsoft. Para obter mais informações,
consulte:
Escopos de token de acesso
Início Rápido: Configurar um aplicativo para expor APIs Web
Hospedado com o AAD: escopos de token de acesso (inclui diretrizes sobre
formatos de escopo do AAD App ID URI )
Escopos de token de acesso para Microsoft API do Graph

Pacote de autenticação
Quando um aplicativo é criado para usar contas corporativas ou de estudante
( SingleOrg ), o aplicativo recebe automaticamente uma referência de pacote para a
Biblioteca de Autenticação Microsoft (Microsoft.Authentication.WebAssembly.Msal ). O
pacote fornece um conjunto de primitivos que ajudam o aplicativo a autenticar usuários
e obter tokens para chamar APIs protegidas.

Se adicionar autenticação a um aplicativo, adicione manualmente o


Microsoft.Authentication.WebAssembly.Msal pacote ao aplicativo.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

O Microsoft.Authentication.WebAssembly.Msal pacote adiciona transitivamente o


Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote ao
aplicativo.

Suporte ao serviço de autenticação


O suporte para autenticação de usuários é registrado no contêiner de serviço com o
AddMsalAuthentication método de extensão fornecido pelo
Microsoft.Authentication.WebAssembly.Msal pacote. Esse método configura os
serviços necessários para que o aplicativo interaja com o Identity Provedor (IP).

Program.cs :
C#

builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});

O AddMsalAuthentication método aceita um retorno de chamada para configurar os


parâmetros necessários para autenticar um aplicativo. Os valores necessários para
configurar o aplicativo podem ser obtidos na configuração do AAD quando você
registra o aplicativo.

A configuração é fornecida pelo wwwroot/appsettings.json arquivo:

JSON

{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{TENANT ID}",
"ClientId": "{CLIENT ID}",
"ValidateAuthority": true
}
}

Exemplo:

JSON

{
"AzureAd": {
"Authority":
"https://login.microsoftonline.com/e86c78e2-...-918e0565a45e",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"ValidateAuthority": true
}
}

Escopos de token de acesso


O Blazor WebAssembly modelo não configura automaticamente o aplicativo para
solicitar um token de acesso para uma API segura. Para provisionar um token de acesso
como parte do fluxo de entrada, adicione o escopo aos escopos de token de acesso
padrão do MsalProviderOptions:

C#
builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Especifique escopos adicionais com AdditionalScopesToConsent :

C#

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");

Para obter mais informações, consulte as seções a seguir do artigo Cenários adicionais :

Solicitar tokens de acesso adicionais


Anexar tokens a solicitações de saída

Modo de logon
A estrutura usa como padrão o modo de logon pop-up e volta para o modo de logon
de redirecionamento se um pop-up não puder ser aberto. Configure a MSAL para usar o
modo de logon de redirecionamento definindo a LoginMode propriedade de
MsalProviderOptions como redirect :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.LoginMode = "redirect";
});

A configuração padrão é popup e o valor da cadeia de caracteres não diferencia


maiúsculas de minúsculas.

Importa o arquivo
O Microsoft.AspNetCore.Components.Authorization namespace é disponibilizado em
todo o aplicativo por meio do _Imports.razor arquivo:

razor
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Página de índice
A página Índice ( wwwroot/index.html ) inclui um script que define o
AuthenticationService em JavaScript. AuthenticationService manipula os detalhes de

baixo nível do protocolo OIDC. O aplicativo chama internamente métodos definidos no


script para executar as operações de autenticação.

HTML

<script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>

Componente do aplicativo
O App componente ( App.razor ) é semelhante ao App componente encontrado em
Blazor Server aplicativos:

O CascadingAuthenticationState componente gerencia a exposição do


AuthenticationState ao restante do aplicativo.
O AuthorizeRouteView componente garante que o usuário atual esteja autorizado
a acessar uma determinada página ou renderize o RedirectToLogin componente.
O RedirectToLogin componente gerencia o redirecionamento de usuários não
autorizados para a página de logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


App componente ( App.razor ) não é mostrada nesta seção. Para inspecionar a marcação

do componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o App componente ( App.razor ) no aplicativo gerado.

Inspecione o App componente ( App.razor ) na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente RedirectToLogin
O RedirectToLogin componente ( Shared/RedirectToLogin.razor ):

Gerencia o redirecionamento de usuários não autorizados para a página de logon.


Preserva a URL atual que o usuário está tentando acessar para que ele possa ser
retornado a essa página se a autenticação for bem-sucedida.

razor

@inject NavigationManager Navigation


@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions
>> Options
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin(Options.Get(
Microsoft.Extensions.Options.Options.DefaultName)
.AuthenticationPaths.LogInPath);
}
}

Componente LoginDisplay
O LoginDisplay componente ( Shared/LoginDisplay.razor ) é renderizado no MainLayout
componente ( Shared/MainLayout.razor ) e gerencia os seguintes comportamentos:
Para usuários autenticados:
Exibe o nome de usuário atual.
Oferece um botão para fazer logoff do aplicativo.
Para usuários anônimos, oferece a opção de fazer logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


LoginDisplay componente não é mostrada nesta seção. Para inspecionar a marcação do

componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão do ASP.NET Core que você pretende usar.
Inspecione o LoginDisplay componente no aplicativo gerado.

Inspecione o LoginDisplay componente na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente de autenticação
A página produzida pelo Authentication componente ( Pages/Authentication.razor )
define as rotas necessárias para lidar com diferentes estágios de autenticação.

O RemoteAuthenticatorView componente:

É fornecido pelo
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote.
Gerencia a execução das ações apropriadas em cada estágio de autenticação.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code {
[Parameter]
public string Action { get; set; }
}

Solucionar problemas

Log
Para habilitar o log de depuração ou rastreamento para Blazor WebAssembly
autenticação no ASP.NET Core 7.0 ou posterior, consulte ASP.NET Core Blazor registro
em log.

Erros comuns
Configuração incorreta do aplicativo ou Identity provedor (IP)

Os erros mais comuns são causados pela configuração incorreta. A seguir, estão
alguns exemplos:
Dependendo dos requisitos do cenário, uma Autoridade, Instância, ID do
Locatário, Domínio do Locatário, ID do Cliente ou URI de Redirecionamento
ausente ou incorreto impede que um aplicativo autentique clientes.
Um escopo de token de acesso incorreto impede que os clientes acessem
pontos de extremidade da API Web do servidor.
Permissões incorretas ou ausentes da API do servidor impedem que os clientes
acessem pontos de extremidade da API Web do servidor.
A execução do aplicativo em uma porta diferente da está configurada no URI de
Redirecionamento do Identity registro de aplicativo do Provedor.

As seções de configuração das diretrizes deste artigo mostram exemplos da


configuração correta. Verifique cuidadosamente cada seção do artigo em busca de
configuração incorreta de aplicativo e IP.

Se a configuração parecer correta:

Analisar logs do aplicativo.

Examine o tráfego de rede entre o aplicativo cliente e o aplicativo IP ou servidor


com as ferramentas de desenvolvedor do navegador. Geralmente, uma
mensagem de erro exata ou uma mensagem com uma pista do que está
causando o problema é retornada ao cliente pelo aplicativo IP ou servidor
depois de fazer uma solicitação. Ferramentas de desenvolvedor diretrizes são
encontradas nos seguintes artigos:
Google Chrome (documentação do Google)
Microsoft Edge
Mozilla Firefox (documentação do Mozilla)

Decodificar o conteúdo de um JSToken Web ON (JWT) usado para autenticar


um cliente ou acessar uma API Web do servidor, dependendo de onde o
problema está ocorrendo. Para obter mais informações, consulte Inspecionar o
conteúdo de um JSToken Web ON (JWT).

A equipe de documentação responde a comentários de documentos e bugs em


artigos (abra um problema na seção Comentários desta página ), mas não
consegue fornecer suporte ao produto. Vários fóruns de suporte público estão
disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos
o uso do seguinte:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Os fóruns anteriores não são de propriedade ou controlados por Microsoft.

Para relatórios de bugs de estrutura não confidenciais, não confidenciais e não


confidenciais, abra um problema com a unidade de produto ASP.NET Core . Não
abra um problema com a unidade de produto até que você tenha investigado
minuciosamente a causa de um problema e não possa resolvê-lo por conta própria
e com a ajuda da comunidade em um fórum de suporte público. A unidade de
produto não é capaz de solucionar problemas de aplicativos individuais que foram
interrompidos devido a simples erros de configuração ou casos de uso envolvendo
serviços de terceiros. Se um relatório for confidencial ou confidencial por natureza
ou descrever uma possível falha de segurança no produto que os invasores podem
explorar, consulte Relatando problemas de segurança e bugs (repositório GitHub
dotnet/aspnetcore) .

Cliente não autorizado para o AAD

info: Microsoft. Falha na autorização de


AspNetCore.Authorization.DefaultAuthorizationService[2]. Esses requisitos não
foram atendidos: DenyAnonymousAuthorizationRequirement: requer um
usuário autenticado.

Erro de retorno de chamada de logon do AAD:


Erro: unauthorized_client
Descrição: AADB2C90058: The provided application is not configured to allow
public clients.

Para resolver o erro:

1. No portal do Azure, acesse o manifesto do aplicativo.


2. Defina o allowPublicClient atributo como null ou true .

Cookies e dados do site


Cookieos dados do site e do s podem persistir entre atualizações de aplicativo e
interferir no teste e na solução de problemas. Desmarque o seguinte ao fazer alterações
no código do aplicativo, alterações na conta de usuário com o provedor ou alterações
na configuração do aplicativo do provedor:

Entradas do cookieusuário
Aplicativos cookies
Dados do site armazenados e armazenados em cache

Uma abordagem para impedir que os dados persistentes cookiede s e site interfiram em
testes e solução de problemas é:

Configurar um navegador
Use um navegador para testar que você pode configurar para excluir todos os
cookie dados do site e sempre que o navegador for fechado.
Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer
alteração no aplicativo, usuário de teste ou configuração do provedor.
Use um comando personalizado para abrir um navegador no modo anônimo ou
privado no Visual Studio:
Abra a caixa de diálogo Procurar com no botão Executar do Visual Studio.
Selecione o botão Adicionar.
Forneça o caminho para o navegador no campo Programa . Os caminhos
executáveis a seguir são locais de instalação típicos para Windows 10. Se o
navegador estiver instalado em um local diferente ou você não estiver usando
Windows 10, forneça o caminho para o executável do navegador.
Microsoft Edge: C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe
Google Chrome: C:\Program Files
(x86)\Google\Chrome\Application\chrome.exe
Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
No campo Argumentos , forneça a opção de linha de comando que o
navegador usa para abrir no modo anônimo ou privado. Alguns navegadores
exigem a URL do aplicativo.
Microsoft Edge: use -inprivate .
Google Chrome: use --incognito --new-window {URL} , em que o espaço
reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Mozilla Firefox: use -private -url {URL} , em que o espaço reservado {URL}
é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Forneça um nome no campo Nome amigável . Por exemplo, Firefox Auth
Testing .

Selecione o botão OK.


Para evitar a necessidade de selecionar o perfil do navegador para cada iteração
de teste com um aplicativo, defina o perfil como o padrão com o botão Definir
como Padrão .
Verifique se o navegador está fechado pelo IDE para qualquer alteração no
aplicativo, usuário de teste ou configuração do provedor.

Atualizações de aplicativos
Um aplicativo funcional pode falhar imediatamente após atualizar o SDK do .NET Core
no computador de desenvolvimento ou alterar as versões do pacote dentro do
aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao
executar atualizações principais. A maioria desses problemas pode ser corrigida
seguindo estas instruções:

1. Limpe os caches de pacote NuGet do sistema local executando dotnet nuget locals
all --clear de um shell de comando.
2. Exclua as pastas e obj do bin projeto.
3. Restaure e recompile o projeto.
4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar
o aplicativo.

7 Observação

Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de


destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do
NuGet ou o Gerenciador de Pacotes FuGet .

Executar o aplicativo Servidor


Ao testar e solucionar problemas de uma solução hospedadaBlazor WebAssembly,
verifique se você está executando o aplicativo do Server projeto. Por exemplo, no Visual
Studio, confirme se o projeto Do servidor está realçado em Gerenciador de Soluções
antes de iniciar o aplicativo com qualquer uma das seguintes abordagens:

Selecione o botão Executar.


Use Depurar>Iniciar Depuração no menu.
Pressione F5 .

Inspecionar o usuário
Os ativos de teste da estrutura ASP.NET Core incluem um Blazor WebAssembly
aplicativo cliente com um User componente que pode ser útil na solução de
problemas. O User componente pode ser usado diretamente em aplicativos ou servir
como base para personalização adicional:

User componente de teste no dotnet/aspnetcore repositório GitHub

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Inspecionar o conteúdo de um JSToken Web ON (JWT)


Para decodificar um JSToken Web ON (JWT), use a ferramenta de jwt.ms do Microsoft .
Os valores na interface do usuário nunca saem do navegador.

Exemplo de JWT codificado (abreviado para exibição):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j ...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q
Exemplo de JWT decodificado pela ferramenta para um aplicativo que se autentica no
Azure AAD B2C:

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Recursos adicionais
Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly
Criar uma versão personalizada da biblioteca JavaScript Authentication.MSAL
Solicitações de API Web não autenticadas ou não autorizadas em um aplicativo
com um cliente padrão seguro
ASP.NET Core Blazor WebAssembly com grupos e funções do Azure Active
Directory
Plataforma de identidade da Microsoft e Azure Active Directory com ASP.NET Core
Documentação da plataforma de identidade da Microsoft
Proteger um aplicativo autônomo
ASP.NET Core Blazor WebAssembly com
o Azure Active Directory B2C
Artigo • 21/12/2022 • 61 minutos para o fim da leitura

Este artigo explica como criar um aplicativo autônomo Blazor WebAssembly que usa o
AAD (Azure Active Directory) B2C para autenticação.

Crie um locatário ou identifique um locatário B2C existente para o aplicativo usar no


portal do Azure seguindo as diretrizes no artigo Criar um locatário B2C do AAD
(documentação do Azure). Retorne a este artigo imediatamente após criar ou identificar
um locatário a ser usado.

Registre as seguintes informações:

Instância do AAD B2C (por exemplo, https://contoso.b2clogin.com/ , que inclui a


barra à direita): a instância é o esquema e o host de um registro de aplicativo B2C
do Azure, que pode ser encontrado abrindo a janela Pontos de Extremidade da
página Registros de aplicativo no portal do Azure.
Domínio primário/publicador/locatário do AAD B2C (por exemplo,
contoso.onmicrosoft.com ): o domínio está disponível como o domínio Publicador

na folha Identidade visual do portal do Azure para o aplicativo registrado.

Registrar um aplicativo AAD B2C:

1. Navegue até Azure Active Directory no portal do Azure. Selecione Registros de


aplicativo na barra lateral. Selecione o botão Novo registro .
2. Forneça um Nome para o aplicativo (por exemplo, Blazor AAD B2C autônomo).
3. Para Tipos de conta com suporte, selecione a opção multilocatário: Contas em
qualquer diretório organizacional ou em qualquer provedor de identidade. Para
autenticar usuários com Azure AD B2C.
4. Defina a lista suspensa URI de redirecionamento como SPA (aplicativo de página
única) e forneça o seguinte URI de redirecionamento:
https://localhost/authentication/login-callback . Se você souber o URI de
redirecionamento de produção para o host padrão do Azure (por exemplo,
azurewebsites.net ) ou o host de domínio personalizado (por exemplo,
contoso.com ), também poderá adicionar o URI de redirecionamento de produção

ao mesmo tempo em que estiver fornecendo o localhost URI de


redirecionamento. Certifique-se de incluir o número da porta para portas não :443
em qualquer URIs de redirecionamento de produção que você adicionar.
5. Se você estiver usando um domínio publicador não verificado, confirme se
Permissões>Conceder consentimento do administrador para permissões openid
e offline_access estão selecionadas . Se o domínio do editor for verificado, essa
caixa de seleção não estará presente.
6. Selecione Registrar.

7 Observação

Não é necessário fornecer o número da porta para um localhost URI de


redirecionamento do AAD B2C. Para obter mais informações, consulte Restrições e
limitações do URI de redirecionamento (URL de resposta): exceções de localhost
(documentação do Azure).

Registre a ID do aplicativo (cliente) (por exemplo, 41451fa7-82d9-4673-8fa5-


69eff5a761fd ).

EmConfigurações> da Plataforma de Autenticação>Aplicativo de página única (SPA):

1. Confirme se o URI de Redirecionamento de


https://localhost/authentication/login-callback está presente.
2. Na seção Concessão implícita , verifique se as caixas de seleção para tokens de
acesso e tokens de IDnão estão selecionadas.
3. Os padrões restantes para o aplicativo são aceitáveis para essa experiência.
4. Selecione o botão Salvar.

No Home> Azure ADfluxos de usuárioB2C>:

Criar um fluxo de usuário de inscrição e de entrada

No mínimo, selecione o atributo de usuário Nome deExibição de Declarações> do


Aplicativo para preencher o context.User.Identity.Name no LoginDisplay componente
( Shared/LoginDisplay.razor ).

Registre o nome de fluxo de usuário de inscrição e entrada criado para o aplicativo (por
exemplo, B2C_1_signupsignin ).

Em uma pasta vazia, substitua os espaços reservados no comando a seguir pelas


informações registradas anteriormente e execute o comando em um shell de comando:

CLI do .NET
dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C
INSTANCE}" --client-id "{CLIENT ID}" --domain "{TENANT DOMAIN}" -o {APP
NAME} -ssp "{SIGN UP OR SIGN IN POLICY}"

Espaço reservado Nome do portal do Azure Exemplo

{AAD B2C INSTANCE} Instância https://contoso.b2clogin.com/ (inclui a


barra à direita)

{APP NAME} — BlazorSample

{CLIENT ID} ID do aplicativo (cliente) 41451fa7-82d9-4673-8fa5-69eff5a761fd

{SIGN UP OR SIGN IN Fluxo de usuário de B2C_1_signupsignin1


POLICY} inscrição/entrada

{TENANT DOMAIN} Domínio contoso.onmicrosoft.com


primário/publicador/locatário

A localização de saída especificada com a opção -o|--output criará uma pasta de


projeto se ela não existir e se tornará parte do nome do aplicativo.

Adicione um par de MsalProviderOptions para openid e


offline_access DefaultAccessTokenScopes:

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
});

Depois de criar o aplicativo, você deve ser capaz de:

Faça logon no aplicativo usando uma conta de usuário do AAD.


Solicitar tokens de acesso para APIs de Microsoft. Para obter mais informações,
consulte:
Escopos de token de acesso
Início Rápido: configurar um aplicativo para expor APIs Web.

Pacote de autenticação
Quando um aplicativo é criado para usar uma Conta B2C Individual ( IndividualB2C ), o
aplicativo recebe automaticamente uma referência de pacote para a Biblioteca de
Autenticação Microsoft (Microsoft.Authentication.WebAssembly.Msal ). O pacote
fornece um conjunto de primitivos que ajudam o aplicativo a autenticar usuários e obter
tokens para chamar APIs protegidas.

Se adicionar autenticação a um aplicativo, adicione manualmente o


Microsoft.Authentication.WebAssembly.Msal pacote ao aplicativo.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

O Microsoft.Authentication.WebAssembly.Msal pacote adiciona transitivamente o


Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote ao
aplicativo.

Suporte ao serviço de autenticação


O suporte para autenticação de usuários é registrado no contêiner de serviço com o
AddMsalAuthentication método de extensão fornecido pelo
Microsoft.Authentication.WebAssembly.Msal pacote. Esse método configura todos os
serviços necessários para que o aplicativo interaja com o Identity Provedor (IP).

Program.cs :

C#

builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C",
options.ProviderOptions.Authentication);
});

O AddMsalAuthentication método aceita um retorno de chamada para configurar os


parâmetros necessários para autenticar um aplicativo. Os valores necessários para
configurar o aplicativo podem ser obtidos na configuração do AAD quando você
registra o aplicativo.
A configuração é fornecida pelo wwwroot/appsettings.json arquivo:

JSON

{
"AzureAdB2C": {
"Authority": "{AAD B2C INSTANCE}{DOMAIN}/{SIGN UP OR SIGN IN POLICY}",
"ClientId": "{CLIENT ID}",
"ValidateAuthority": false
}
}

Na configuração anterior, o {AAD B2C INSTANCE} inclui uma barra à direita.

Exemplo:

JSON

{
"AzureAdB2C": {
"Authority":
"https://contoso.b2clogin.com/contoso.onmicrosoft.com/B2C_1_signupsignin1",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"ValidateAuthority": false
}
}

Escopos de token de acesso


O Blazor WebAssembly modelo não configura automaticamente o aplicativo para
solicitar um token de acesso para uma API segura. Para provisionar um token de acesso
como parte do fluxo de entrada, adicione o escopo aos escopos de token de acesso
padrão do MsalProviderOptions:

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Especifique escopos adicionais com AdditionalScopesToConsent :

C#
options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");

Para obter mais informações, consulte as seções a seguir do artigo Cenários adicionais :

Solicitar tokens de acesso adicionais


Anexar tokens a solicitações de saída

Modo de logon
A estrutura usa como padrão o modo de logon pop-up e volta para o modo de logon
de redirecionamento se um pop-up não puder ser aberto. Configure a MSAL para usar o
modo de logon de redirecionamento definindo a LoginMode propriedade de
MsalProviderOptions como redirect :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.LoginMode = "redirect";
});

A configuração padrão é popup e o valor da cadeia de caracteres não diferencia


maiúsculas de minúsculas.

Importa o arquivo
O Microsoft.AspNetCore.Components.Authorization namespace é disponibilizado em
todo o aplicativo por meio do _Imports.razor arquivo:

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared
Página de índice
A página Índice ( wwwroot/index.html ) inclui um script que define o
AuthenticationService em JavaScript. AuthenticationService manipula os detalhes de

baixo nível do protocolo OIDC. O aplicativo chama internamente métodos definidos no


script para executar as operações de autenticação.

HTML

<script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>

Componente do aplicativo
O App componente ( App.razor ) é semelhante ao App componente encontrado em
Blazor Server aplicativos:

O CascadingAuthenticationState componente gerencia a exposição do


AuthenticationState ao restante do aplicativo.
O AuthorizeRouteView componente garante que o usuário atual esteja autorizado
a acessar uma determinada página ou renderize o RedirectToLogin componente.
O RedirectToLogin componente gerencia o redirecionamento de usuários não
autorizados para a página de logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


App componente ( App.razor ) não é mostrada nesta seção. Para inspecionar a marcação
do componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o App componente ( App.razor ) no aplicativo gerado.

Inspecione o App componente ( App.razor ) na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente RedirectToLogin
O RedirectToLogin componente ( Shared/RedirectToLogin.razor ):

Gerencia o redirecionamento de usuários não autorizados para a página de logon.


Preserva a URL atual que o usuário está tentando acessar para que ele possa ser
retornado a essa página se a autenticação for bem-sucedida.

razor

@inject NavigationManager Navigation


@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions
>> Options
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin(Options.Get(
Microsoft.Extensions.Options.Options.DefaultName)
.AuthenticationPaths.LogInPath);
}
}

Componente LoginDisplay
O LoginDisplay componente ( Shared/LoginDisplay.razor ) é renderizado no MainLayout
componente ( Shared/MainLayout.razor ) e gerencia os seguintes comportamentos:

Para usuários autenticados:


Exibe o nome de usuário atual.
Oferece um botão para sair do aplicativo.
Para usuários anônimos, oferece a opção de fazer logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação do


LoginDisplay componente não é mostrada nesta seção. Para inspecionar a marcação do

componente para uma determinada versão, use uma das seguintes abordagens:
Crie um aplicativo provisionado para autenticação do modelo de projeto padrão
Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o LoginDisplay componente no aplicativo gerado.

Inspecione o LoginDisplay componente na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente de autenticação
A página produzida pelo Authentication componente ( Pages/Authentication.razor )
define as rotas necessárias para lidar com diferentes estágios de autenticação.

O RemoteAuthenticatorView componente:

É fornecido pelo
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote.
Gerencia a execução das ações apropriadas em cada estágio de autenticação.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code {
[Parameter]
public string Action { get; set; }
}

Fluxos de usuário personalizados


A Biblioteca de Autenticação Microsoft (Microsoft.Authentication.WebAssembly.Msal,
pacote NuGet ) não dá suporte a fluxos de usuário do AAD B2C por padrão. Crie fluxos
de usuário personalizados no código do desenvolvedor.

Para obter mais informações sobre como criar um desafio para um fluxo de usuário
personalizado, consulte Fluxos de usuário no Azure Active Directory B2C.

Solucionar problemas

Log
Para habilitar o log de depuração ou rastreamento para Blazor WebAssembly
autenticação no ASP.NET Core 7.0 ou posterior, consulte ASP.NET Core Blazor registro
em log.

Erros comuns
Configuração incorreta do aplicativo ou Identity provedor (IP)

Os erros mais comuns são causados pela configuração incorreta. A seguir, estão
alguns exemplos:
Dependendo dos requisitos do cenário, uma Autoridade ausente ou incorreta,
Instância, ID do Locatário, Domínio do Locatário, ID do Cliente ou URI de
Redirecionamento impede que um aplicativo autentique clientes.
Um escopo de token de acesso incorreto impede que os clientes acessem
pontos de extremidade da API Web do servidor.
Permissões de API de servidor incorretas ou ausentes impedem que os clientes
acessem pontos de extremidade da API Web do servidor.
Executar o aplicativo em uma porta diferente do configurado no URI de
Redirecionamento do Identity registro de aplicativo do Provedor.

As seções de configuração das diretrizes deste artigo mostram exemplos da


configuração correta. Verifique cuidadosamente cada seção do artigo em busca de
configuração incorreta de aplicativo e IP.

Se a configuração aparecer correta:

Analisar logs de aplicativo.

Examine o tráfego de rede entre o aplicativo cliente e o aplicativo IP ou servidor


com as ferramentas de desenvolvedor do navegador. Muitas vezes, uma
mensagem de erro exata ou uma mensagem com uma pista do que está
causando o problema é retornada ao cliente pelo aplicativo IP ou servidor
depois de fazer uma solicitação. Ferramentas de desenvolvedor diretrizes são
encontradas nos seguintes artigos:
Google Chrome (documentação do Google)
Microsoft Edge
Mozilla Firefox (documentação do Mozilla)

Decodificar o conteúdo de um JSToken Web ON (JWT) usado para autenticar


um cliente ou acessar uma API Web do servidor, dependendo de onde o
problema está ocorrendo. Para obter mais informações, consulte Inspecionar o
conteúdo de um JSToken Web ON (JWT).

A equipe de documentação responde a comentários de documentos e bugs em


artigos (abra um problema na seção Comentários desta página ), mas não
consegue fornecer suporte ao produto. Vários fóruns de suporte público estão
disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos
o uso do seguinte:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Os fóruns anteriores não são de propriedade ou controlados por Microsoft.

Para relatórios de bugs de estrutura reproduzível não confidenciais, não


confidenciais e não confidenciais, abra um problema com a unidade do produto
ASP.NET Core . Não abra um problema com a unidade do produto até que você
investigue minuciosamente a causa de um problema e não possa resolvê-lo por
conta própria e com a ajuda da comunidade em um fórum de suporte público. A
unidade do produto não é capaz de solucionar problemas de aplicativos
individuais que estão quebrados devido a simples configuração incorreta ou casos
de uso envolvendo serviços de terceiros. Se um relatório for confidencial ou
confidencial por natureza ou descrever uma possível falha de segurança no
produto que os invasores podem explorar, consulte Relatando problemas de
segurança e bugs (repositório GitHub dotnet/aspnetcore) .

Cliente não autorizado para o AAD

info: Microsoft. Falha na autorização de


AspNetCore.Authorization.DefaultAuthorizationService[2]. Esses requisitos não
foram atendidos: DenyAnonymousAuthorizationRequirement: requer um
usuário autenticado.

Erro de retorno de chamada de logon do AAD:


Erro: unauthorized_client
Descrição: AADB2C90058: The provided application is not configured to allow
public clients.

Para resolver o erro:

1. No portal do Azure, acesse o manifesto do aplicativo.


2. Defina o allowPublicClient atributo como null ou true .

Cookies e dados do site


Cookies e dados do site podem persistir entre as atualizações do aplicativo e interferir
no teste e na solução de problemas. Desmarque o seguinte ao fazer alterações no
código do aplicativo, alterações na conta de usuário com o provedor ou alterações na
configuração do aplicativo do provedor:

Entradas do cookieusuário
Aplicativos cookie
Dados do site armazenados e armazenados em cache

Uma abordagem para impedir que os dados persistentes cookiedo site e do site
interfiram no teste e na solução de problemas é:

Configurar um navegador
Use um navegador para testar que você pode configurar para excluir todos os
cookie dados do site e sempre que o navegador for fechado.
Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer
alteração no aplicativo, usuário de teste ou configuração do provedor.
Use um comando personalizado para abrir um navegador no modo anônimo ou
privado no Visual Studio:
Abra a caixa de diálogo Procurar com no botão Executar do Visual Studio.
Selecione o botão Adicionar.
Forneça o caminho para o navegador no campo Programa . Os seguintes
caminhos executáveis são locais de instalação típicos para Windows 10. Se o
navegador estiver instalado em um local diferente ou você não estiver usando
Windows 10, forneça o caminho para o executável do navegador.
Microsoft Edge: C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe

Google Chrome: C:\Program Files


(x86)\Google\Chrome\Application\chrome.exe

Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe


No campo Argumentos , forneça a opção de linha de comando que o
navegador usa para abrir no modo anônimo ou privado. Alguns navegadores
exigem a URL do aplicativo.
Microsoft Edge: use -inprivate .
Google Chrome: use --incognito --new-window {URL} , em que o espaço
reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Mozilla Firefox: use -private -url {URL} , em que o espaço reservado {URL}
é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Forneça um nome no campo Nome amigável . Por exemplo, Firefox Auth
Testing .

Selecione o botão OK.


Para evitar a necessidade de selecionar o perfil do navegador para cada iteração
de teste com um aplicativo, defina o perfil como o padrão com o botão Definir
como Padrão .
Verifique se o navegador está fechado pelo IDE para qualquer alteração na
configuração do aplicativo, do usuário de teste ou do provedor.

Atualizações de aplicativos
Um aplicativo funcional pode falhar imediatamente após atualizar o SDK do .NET Core
no computador de desenvolvimento ou alterar as versões do pacote dentro do
aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao
executar atualizações principais. A maioria desses problemas pode ser corrigida
seguindo estas instruções:

1. Limpe os caches de pacote NuGet do sistema local executando dotnet nuget locals
all --clear de um shell de comando.
2. Exclua as pastas e obj do bin projeto.
3. Restaure e recompile o projeto.
4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar
o aplicativo.

7 Observação

Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de


destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do
NuGet ou o Gerenciador de Pacotes FuGet .

Executar o aplicativo Servidor


Ao testar e solucionar problemas de uma solução hospedadaBlazor WebAssembly,
verifique se você está executando o aplicativo do Server projeto. Por exemplo, no Visual
Studio, confirme se o projeto Do servidor está realçado em Gerenciador de Soluções
antes de iniciar o aplicativo com qualquer uma das seguintes abordagens:

Selecione o botão Executar.


Use Depurar>Iniciar Depuração no menu.
Pressione F5 .

Inspecionar o usuário
Os ativos de teste da estrutura ASP.NET Core incluem um Blazor WebAssembly
aplicativo cliente com um User componente que pode ser útil na solução de
problemas. O User componente pode ser usado diretamente em aplicativos ou servir
como base para personalização adicional:

User componente de teste no dotnet/aspnetcore repositório GitHub

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Inspecionar o conteúdo de um JSToken Web ON (JWT)


Para decodificar um JSToken Web ON (JWT), use a ferramenta de jwt.ms do Microsoft .
Os valores na interface do usuário nunca saem do navegador.

Exemplo de JWT codificado (abreviado para exibição):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j ...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q
Exemplo de JWT decodificado pela ferramenta para um aplicativo que se autentica no
Azure AAD B2C:

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Recursos adicionais
Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly
Criar uma versão personalizada da biblioteca JavaScript Authentication.MSAL
Solicitações de API Web não autenticadas ou não autorizadas em um aplicativo
com um cliente padrão seguro
Autenticação na nuvem com o Azure Active Directory B2C no ASP.NET Core
Tutorial: Criar um locatário do Azure Active Directory B2C
Tutorial: Registrar um aplicativo no Azure Active Directory B2C
Documentação da plataforma de identidade da Microsoft
Proteger um aplicativo de ASP.NET Core
Blazor WebAssembly hospedado com o
Azure Active Directory
Artigo • 21/12/2022 • 98 minutos para o fim da leitura

Este artigo explica como criar uma solução hospedada Blazor WebAssembly que usa o
AAD (Azure Active Directory) para autenticação.

Para obter mais informações sobre as soluções, consulte Ferramentas para ASP.NET Core
Blazor.

Registrar aplicativos no AAD e criar solução

Criar um locatário
Siga as diretrizes no Início Rápido: configurar um locatário para criar um locatário no
AAD.

Registrar um aplicativo de API do servidor


Registre um aplicativo do AAD para o aplicativo de API do Servidor:

1. Navegue até Azure Active Directory no portal do Azure. Selecione Registros de


aplicativo na barra lateral. Selecione o botão Novo registro .
2. Forneça um Nome para o aplicativo (por exemplo, Blazor Server AAD).
3. Escolha um tipo de conta com suporte. Você pode selecionar Contas somente
neste diretório organizacional (locatário único) para essa experiência.
4. O aplicativo de API do Servidor não requer um URI de Redirecionamento nesse
cenário, portanto, deixe o menu suspenso definido como Web e não insira um URI
de redirecionamento.
5. Se você estiver usando um domínio publicador não verificado, desmarque a caixa
de seleção Permissões>Conceder consentimento do administrador para
permissões openid e offline_access . Se o domínio do editor for verificado, essa
caixa de seleção não estará presente.
6. Selecione Registrar.

Registre as seguintes informações:


Aplicativo de API do servidor ID do aplicativo (cliente) (por exemplo, 41451fa7-
82d9-4673-8fa5-69eff5a761fd )
ID do diretório (locatário) (por exemplo, e86c78e2-8bb4-4c41-aefd-918e0565a45e )
Domínio primário/publicador/locatário do AAD (por exemplo,
contoso.onmicrosoft.com ): o domínio está disponível como o domínio publicador

na folha Identidade visual do portal do Azure para o aplicativo registrado.

Em permissões de API, remova o Microsoft permissãoUser.Read do Graph>, pois o


aplicativo não requer acesso ao perfil de usuário ou entrada.

Em Expor uma API:

1. Selecione Adicionar um escopo.


2. Selecione Salvar e continuar.
3. Forneça um nome de escopo (por exemplo, API.Access ).
4. Forneça um Administração nome de exibição de consentimento (por exemplo,
Access API ).
5. Forneça uma descrição de consentimento Administração (por exemplo, Allows
the app to access server app API endpoints. ).

6. Confirme se o Estado está definido como Habilitado.


7. Selecione Adicionar escopo.

Registre as seguintes informações:

URI da ID do aplicativo (por exemplo, api://41451fa7-82d9-4673-8fa5-


69eff5a761fd , https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-
69eff5a761fd ou o valor personalizado que você fornece)

Nome do escopo (por exemplo, API.Access )

Registrar um aplicativo cliente


Registre um aplicativo AAD para o aplicativo Cliente:

1. Navegue até Azure Active Directory no portal do Azure. Selecione Registros de


aplicativo na barra lateral. Selecione o botão Novo registro .
2. Forneça um Nome para o aplicativo (por exemplo, Blazor AAD do cliente).
3. Escolha um tipo de conta com suporte. Você pode selecionar Contas somente
neste diretório organizacional (locatário único) para essa experiência.
4. Defina a lista suspensa URI de redirecionamento como SPA (aplicativo de página
única) e forneça o seguinte URI de redirecionamento:
https://localhost/authentication/login-callback . Se você souber o URI de

redirecionamento de produção para o host padrão do Azure (por exemplo,


azurewebsites.net ) ou o host de domínio personalizado (por exemplo,

contoso.com ), também poderá adicionar o URI de redirecionamento de produção


ao mesmo tempo em que estiver fornecendo o localhost URI de
redirecionamento. Certifique-se de incluir o número da porta para portas não :443
em qualquer URIs de redirecionamento de produção que você adicionar.
5. Se você estiver usando um domínio publicador não verificado, desmarque a caixa
de seleção Permissões>Conceder consentimento do administrador para
permissões openid e offline_access . Se o domínio do editor for verificado, essa
caixa de seleção não estará presente.
6. Selecione Registrar.

7 Observação

Não é necessário fornecer o número da porta para um localhost URI de


redirecionamento do AAD. Para obter mais informações, consulte Restrições e
limitações do URI de redirecionamento (URL de resposta): exceções de localhost
(documentação do Azure).

Registre a ID do Client aplicativo Aplicativo (cliente) (por exemplo, 4369008b-21fa-427c-


abaa-9b53bf58e538 ).

EmConfigurações> da Plataforma de Autenticação>Aplicativo de página única (SPA):

1. Confirme se o URI de Redirecionamento de


https://localhost/authentication/login-callback está presente.

2. Na seção Concessão implícita , verifique se as caixas de seleção para tokens de


acesso e tokens de IDnão estão selecionadas.
3. Os padrões restantes para o aplicativo são aceitáveis para essa experiência.
4. Selecione o botão Salvar.

Em permissões de API:

1. Confirme se o aplicativo tem Microsoft permissãoUser.Read do Graph>.


2. Selecione Adicionar uma permissão seguida por Minhas APIs.
3. Selecione o aplicativo de API do Servidor na coluna Nome (por exemplo, Blazor
Server AAD).
4. Abra a lista de API .
5. Habilite o acesso à API (por exemplo, API.Access ).
6. Selecione Adicionar Permissões.
7. Selecione o botão Conceder consentimento do administrador para {NOME DO
LOCATÁRIO} . Clique em Sim para confirmar.
) Importante

Se você não tiver autoridade para conceder consentimento de administrador ao


locatário na última etapa da configuração de permissões de API porque o
consentimento para usar o aplicativo é delegado aos usuários, você deve seguir as
seguintes etapas adicionais:

O aplicativo deve usar um domínio de editor confiável.


Server Na configuração do aplicativo no portal do Azure, selecione Expor
uma API. Em Aplicativos cliente autorizados, selecione o botão Para
Adicionar um aplicativo cliente. Adicione a Client ID do aplicativo (cliente)
(por exemplo, 4369008b-21fa-427c-abaa-9b53bf58e538 ).

Criar o aplicativo
Em uma pasta vazia, substitua os espaços reservados no comando a seguir pelas
informações registradas anteriormente e execute o comando em um shell de comando:

CLI do .NET

dotnet new blazorwasm -au SingleOrg --api-client-id "{SERVER API APP CLIENT
ID}" --app-id-uri "{SERVER API APP ID URI}" --client-id "{CLIENT APP CLIENT
ID}" --default-scope "{DEFAULT SCOPE}" --domain "{TENANT DOMAIN}" -ho -o
{APP NAME} --tenant-id "{TENANT ID}"

2 Aviso

Evite usar traços ( - ) no nome {APP NAME} do aplicativo que interrompem a


formação do identificador de aplicativo OIDC. A lógica no Blazor WebAssembly
modelo de projeto usa o nome do projeto para um identificador de aplicativo OIDC
na configuração da solução. Caso Pascal ( BlazorSample ) ou sublinhados
( Blazor_Sample ) são alternativas aceitáveis. Para obter mais informações, consulte
Traços em um nome de projeto hospedado Blazor WebAssembly interrompem a
segurança do OIDC (dotnet/aspnetcore #35337) .

Espaço reservado Nome do portal do Azure Exemplo

{APP NAME} — BlazorSample


Espaço reservado Nome do portal do Azure Exemplo

{CLIENT APP CLIENT ID do aplicativo (cliente) para o Client 4369008b-21fa-427c-abaa-


ID} aplicativo 9b53bf58e538

{DEFAULT SCOPE} Nome do escopo API.Access

{SERVER API APP ID do aplicativo (cliente) para o aplicativo 41451fa7-82d9-4673-8fa5-


CLIENT ID} de API do Servidor 69eff5a761fd

{SERVER API APP ID URI da ID do aplicativo† 41451fa7-82d9-4673-8fa5-


URI} 69eff5a761fd †

{TENANT DOMAIN} Domínio primário/publicador/locatário contoso.onmicrosoft.com

{TENANT ID} ID do diretório (locatário) e86c78e2-8bb4-4c41-aefd-


918e0565a45e

†O Blazor WebAssembly modelo adiciona automaticamente um esquema de ao


argumento URI da api:// ID do Aplicativo passado no dotnet new comando . Ao
fornecer o URI da ID do Aplicativo para o {SERVER API APP ID URI} espaço reservado e
se o esquema for api:// , remova o esquema ( api:// ) do argumento , como mostra o
valor de exemplo na tabela anterior. Se o URI da ID do Aplicativo for um valor
personalizado ou tiver algum outro esquema (por exemplo, https:// para um domínio
de editor não verificado semelhante a https://contoso.onmicrosoft.com/41451fa7-82d9-
4673-8fa5-69eff5a761fd ), você deverá atualizar manualmente o URI de escopo padrão e
remover o api:// esquema depois que o Client aplicativo for criado pelo modelo. Para
obter mais informações, consulte a observação na seção Escopos do token de acesso . O
Blazor WebAssembly modelo pode ser alterado em uma versão futura do ASP.NET Core
para resolver esses cenários. Para obter mais informações, consulte Esquema duplo para
URI de ID de Aplicativo com Blazor modelo WASM (hospedado, organização única)
(dotnet/aspnetcore #27417) .

A localização de saída especificada com a opção -o|--output criará uma pasta de


projeto se ela não existir e se tornará parte do nome do aplicativo. Evite usar traços ( - )
no nome do aplicativo que interrompem a formação do identificador de aplicativo
OIDC (consulte o AVISO anterior).

7 Observação

Uma alteração de configuração pode ser necessária ao usar um locatário do Azure


com um domínio de editor não verificado, que é descrito na seção Configurações
do aplicativo.
Server configuração do aplicativo
Esta seção refere-se ao aplicativo da Server solução.

Pacote de autenticação
O suporte para autenticar e autorizar chamadas para ASP.NET Core APIs Web com a
Plataforma Microsoft Identity é fornecido pelo Microsoft.Identity.Web pacote.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

O Server aplicativo de uma solução hospedada Blazor criada a partir do Blazor


WebAssembly modelo inclui o Microsoft.Identity.Web.UI pacote por padrão. O pacote
adiciona interface do usuário para autenticação de usuário em aplicativos Web e não é
usado pela Blazor estrutura. Se o Server aplicativo não for usado para autenticar
usuários diretamente, é seguro remover a referência de pacote do arquivo de Server
projeto do aplicativo.

Suporte ao serviço de autenticação


O AddAuthentication método configura os serviços de autenticação dentro do aplicativo
e configura o manipulador do Portador JWT como o método de autenticação padrão. O
AddMicrosoftIdentityWebApi método configura serviços para proteger a API Web com
Microsoft Identity Platform v2.0. Esse método espera uma AzureAd seção na
configuração do aplicativo com as configurações necessárias para inicializar as opções
de autenticação.

C#

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));

7 Observação
Quando um único esquema de autenticação é registrado, o esquema de
autenticação é usado automaticamente como o esquema padrão do aplicativo e
não é necessário declarar o esquema para AddAuthentication ou por meio
AuthenticationOptionsde . Para obter mais informações, consulte Visão geral da
Autenticação ASP.NET Core e o comunicado de ASP.NET Core
(aspnet/Announcements #490) .

UseAuthentication e UseAuthorization certifique-se de que:

O aplicativo tenta analisar e validar tokens em solicitações de entrada.


Qualquer solicitação que tente acessar um recurso protegido sem credenciais
adequadas falha.

C#

app.UseAuthentication();
app.UseAuthorization();

User.Identity. Nome
Por padrão, a API do Server aplicativo é preenchida User.Identity.Name com o valor do
tipo de http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name declaração (por
exemplo, 2d64b3da-d9d5-42c6-9352-53d8df33d770@contoso.onmicrosoft.com ).

Para configurar o aplicativo para receber o valor do tipo de declaração name :

Adicione um namespace para Microsoft.AspNetCore.Authentication.JwtBearer a


Program.cs :

C#

using Microsoft.AspNetCore.Authentication.JwtBearer;

Configure o TokenValidationParameters.NameClaimType do JwtBearerOptions em


Program.cs :

C#

builder.Services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.NameClaimType = "name";
});
Configurações de aplicativo
O appsettings.json arquivo contém as opções para configurar o manipulador de
portador JWT usado para validar tokens de acesso:

JSON

{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "{DOMAIN}",
"TenantId": "{TENANT ID}",
"ClientId": "{SERVER API APP CLIENT ID}",
"CallbackPath": "/signin-oidc"
}
}

Exemplo:

JSON

{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "contoso.onmicrosoft.com",
"TenantId": "e86c78e2-8bb4-4c41-aefd-918e0565a45e",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"CallbackPath": "/signin-oidc"
}
}

Ao trabalhar com uma API de servidor registrada com o AAD e o registro do AAD do
aplicativo estiver em um locatário que depende de um domínio publicador não
verificado, o URI da ID do aplicativo do aplicativo de API do servidor não api://{SERVER
API APP CLIENT ID OR CUSTOM VALUE} está, mas está no formato

https://{TENANT}.onmicrosoft.com/{SERVER API APP CLIENT ID OR CUSTOM VALUE} . Se


esse for o caso, o escopo do token de Client acesso padrão no Program.cs aplicativo
será semelhante ao seguinte:

C#

options.ProviderOptions.DefaultAccessTokenScopes
.Add("https://{TENANT}.onmicrosoft.com/{SERVER API APP CLIENT ID OR
CUSTOM VALUE}/{DEFAULT SCOPE}");
Para configurar o aplicativo de API do servidor para um público correspondente, defina
o Audience Server no arquivo de configurações do aplicativo de API ( appsettings.json )
para corresponder ao público-alvo do aplicativo fornecido pelo portal do Azure:

JSON

{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{TENANT ID}",
"ClientId": "{SERVER API APP CLIENT ID}",
"ValidateAuthority": true,
"Audience": "https://{TENANT}.onmicrosoft.com/{SERVER API APP CLIENT ID
OR CUSTOM VALUE}"
}
}

Na configuração anterior, o final do Audience valor não inclui o escopo /{DEFAULT


SCOPE} padrão .

Exemplo:

No Program.cs do Client aplicativo:

C#

options.ProviderOptions.DefaultAccessTokenScopes
.Add("https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-
69eff5a761fd/API.Access");

Definir o arquivo de configurações do Server aplicativo de API ( appsettings.json ) com


um público-alvo correspondente ( Audience ):

JSON

{
"AzureAd": {
"Authority":
"https://login.microsoftonline.com/e86c78e2-...-918e0565a45e",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"ValidateAuthority": true,
"Audience": "https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-
69eff5a761fd"
}
}

No exemplo anterior, o final do Audience valor não inclui o escopo /API.Access padrão .
Controlador WeatherForecast
O controlador WeatherForecast ( Controllers/WeatherForecastController.cs ) expõe uma
API protegida com o [Authorize] atributo aplicado ao controlador. É importante
entender que:

O [Authorize] atributo nesse controlador de API é a única coisa que protege essa
API contra acesso não autorizado.
O [Authorize] atributo usado no Blazor WebAssembly aplicativo serve apenas
como uma dica para o aplicativo de que o usuário deve ser autorizado para que o
aplicativo funcione corretamente.

C#

[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
...
}
}

Client configuração do aplicativo


Esta seção refere-se ao aplicativo da Client solução.

Pacote de autenticação
Quando um aplicativo é criado para usar contas corporativas ou de estudante
( SingleOrg ), o aplicativo recebe automaticamente uma referência de pacote para a
Biblioteca de Autenticação Microsoft (Microsoft.Authentication.WebAssembly.Msal ). O
pacote fornece um conjunto de primitivos que ajudam o aplicativo a autenticar usuários
e obter tokens para chamar APIs protegidas.

Se adicionar autenticação a um aplicativo, adicione manualmente o


Microsoft.Authentication.WebAssembly.Msal pacote ao aplicativo.

7 Observação
Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

O Microsoft.Authentication.WebAssembly.Msal pacote adiciona transitivamente o


Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote ao
aplicativo.

Suporte ao serviço de autenticação


O suporte para HttpClient instâncias é adicionado que incluem tokens de acesso ao
fazer solicitações ao projeto do servidor.

Program.cs :

C#

builder.Services.AddHttpClient("{APP ASSEMBLY}.ServerAPI", client =>


client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()


.CreateClient("{APP ASSEMBLY}.ServerAPI"));

O espaço reservado {APP ASSEMBLY} é o nome do assembly do aplicativo (por exemplo,


BlazorSample.Client ).

O suporte para autenticação de usuários é registrado no contêiner de serviço com o


AddMsalAuthentication método de extensão fornecido pelo
Microsoft.Authentication.WebAssembly.Msal pacote. Esse método configura os
serviços necessários para que o aplicativo interaja com o Identity Provedor (IP).

Program.cs :

C#

builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});
O AddMsalAuthentication método aceita um retorno de chamada para configurar os
parâmetros necessários para autenticar um aplicativo. Os valores necessários para
configurar o aplicativo podem ser obtidos na configuração do AAD do Portal do Azure
quando você registra o aplicativo.

A configuração é fornecida pelo wwwroot/appsettings.json arquivo:

JSON

{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{TENANT ID}",
"ClientId": "{CLIENT APP CLIENT ID}",
"ValidateAuthority": true
}
}

Exemplo:

JSON

{
"AzureAd": {
"Authority":
"https://login.microsoftonline.com/e86c78e2-...-918e0565a45e",
"ClientId": "4369008b-21fa-427c-abaa-9b53bf58e538",
"ValidateAuthority": true
}
}

Escopos de token de acesso


Os escopos de token de acesso padrão representam a lista de escopos de token de
acesso que são:

Incluído por padrão na solicitação de entrada.


Usado para provisionar um token de acesso imediatamente após a autenticação.

Todos os escopos devem pertencer ao mesmo aplicativo de acordo com as regras do


Azure Active Directory. Escopos adicionais podem ser adicionados para aplicativos de
API adicionais, conforme necessário:

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

7 Observação

O Blazor WebAssembly modelo adiciona automaticamente um esquema de ao


argumento URI da api:// ID do Aplicativo passado no dotnet new comando . Ao
gerar um aplicativo do Blazor modelo de projeto, confirme se o valor do escopo
do token de acesso padrão usa o valor de URI de ID de Aplicativo personalizado
correto fornecido no portal do Azure ou um valor com um dos seguintes formatos:

Quando o domínio do editor do diretório é confiável, o escopo do token de


acesso padrão normalmente é um valor semelhante ao exemplo a seguir, em
que API.Access é o nome de escopo padrão:

C#

options.ProviderOptions.DefaultAccessTokenScopes.Add(
"api://41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");

Inspecione o valor de um esquema duplo ( api://api://... ). Se um esquema


duplo estiver presente, remova o primeiro api:// esquema do valor.

Quando o domínio do editor do diretório não é confiável, o escopo do token


de acesso padrão normalmente é um valor semelhante ao exemplo a seguir,
em que API.Access é o nome de escopo padrão:

C#

options.ProviderOptions.DefaultAccessTokenScopes.Add(
"https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-
69eff5a761fd/API.Access");

Inspecione o valor de um esquema extra api://


( api://https://contoso.onmicrosoft.com/... ). Se um esquema extra api://
estiver presente, remova o api:// esquema do valor .

O Blazor WebAssembly modelo pode ser alterado em uma versão futura do


ASP.NET Core para resolver esses cenários. Para obter mais informações, consulte
Esquema duplo para URI de ID de Aplicativo com Blazor modelo WASM
(hospedado, organização única) (dotnet/aspnetcore #27417) .
Especifique escopos adicionais com AdditionalScopesToConsent :

C#

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");

Para obter mais informações, consulte as seções a seguir do artigo Cenários adicionais :

Solicitar tokens de acesso adicionais


Anexar tokens a solicitações de saída

Modo de logon
A estrutura usa como padrão o modo de logon pop-up e volta para o modo de logon
de redirecionamento se um pop-up não puder ser aberto. Configure a MSAL para usar o
modo de logon de redirecionamento definindo a LoginMode propriedade de
MsalProviderOptions como redirect :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.LoginMode = "redirect";
});

A configuração padrão é popup e o valor da cadeia de caracteres não diferencia


maiúsculas de minúsculas.

Importa o arquivo
O Microsoft.AspNetCore.Components.Authorization namespace é disponibilizado em
todo o aplicativo por meio do _Imports.razor arquivo:

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}.Client
@using {APPLICATION ASSEMBLY}.Client.Shared

Página de índice
A página Índice ( wwwroot/index.html ) inclui um script que define o
AuthenticationService em JavaScript. AuthenticationService manipula os detalhes de

baixo nível do protocolo OIDC. O aplicativo chama internamente métodos definidos no


script para executar as operações de autenticação.

HTML

<script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>

Componente do aplicativo
O App componente ( App.razor ) é semelhante ao App componente encontrado em
Blazor Server aplicativos:

O CascadingAuthenticationState componente gerencia a exposição do


AuthenticationState ao restante do aplicativo.
O AuthorizeRouteView componente garante que o usuário atual esteja autorizado
a acessar uma determinada página ou renderize o RedirectToLogin componente.
O RedirectToLogin componente gerencia o redirecionamento de usuários não
autorizados para a página de logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


App componente ( App.razor ) não é mostrada nesta seção. Para inspecionar a marcação

do componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o App componente ( App.razor ) no aplicativo gerado.

Inspecione o App componente ( App.razor ) na origem de referência .

7 Observação
Os links de documentação para a fonte de referência do .NET geralmente
carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente RedirectToLogin
O RedirectToLogin componente ( Shared/RedirectToLogin.razor ):

Gerencia o redirecionamento de usuários não autorizados para a página de logon.


Preserva a URL atual que o usuário está tentando acessar para que ele possa ser
retornado a essa página se a autenticação for bem-sucedida.

razor

@inject NavigationManager Navigation


@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions
>> Options
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin(Options.Get(
Microsoft.Extensions.Options.Options.DefaultName)
.AuthenticationPaths.LogInPath);
}
}

Componente LoginDisplay
O LoginDisplay componente ( Shared/LoginDisplay.razor ) é renderizado no MainLayout
componente ( Shared/MainLayout.razor ) e gerencia os seguintes comportamentos:

Para usuários autenticados:


Exibe o nome de usuário atual.
Oferece um botão para sair do aplicativo.
Para usuários anônimos, oferece a opção de fazer logon.
Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação do
LoginDisplay componente não é mostrada nesta seção. Para inspecionar a marcação do
componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o LoginDisplay componente no aplicativo gerado.

Inspecione o LoginDisplay componente na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente de autenticação
A página produzida pelo Authentication componente ( Pages/Authentication.razor )
define as rotas necessárias para lidar com diferentes estágios de autenticação.

O RemoteAuthenticatorView componente:

É fornecido pelo
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote.
Gerencia a execução das ações apropriadas em cada estágio de autenticação.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code {
[Parameter]
public string Action { get; set; }
}

Componente FetchData
O FetchData componente mostra como:

Provisionar um token de acesso.


Use o token de acesso para chamar uma API de recurso protegido no aplicativo
Servidor .

A @attribute [Authorize] diretiva indica ao Blazor WebAssembly sistema de autorização


que o usuário deve ser autorizado para visitar esse componente. A presença do atributo
no Client aplicativo não impede que a API no servidor seja chamada sem credenciais
adequadas. O Server aplicativo também deve usar [Authorize] nos pontos de
extremidade apropriados para protegê-los corretamente.

IAccessTokenProvider.RequestAccessToken cuida da solicitação de um token de acesso


que pode ser adicionado à solicitação para chamar a API. Se o token for armazenado em
cache ou se o serviço puder provisionar um novo token de acesso sem interação do
usuário, a solicitação de token terá êxito. Caso contrário, a solicitação de token falhará
com um AccessTokenNotAvailableException, que é capturado em uma try-catch
instrução .

Para obter o token real a ser incluído na solicitação, o aplicativo deve verificar se a
solicitação foi bem-sucedida chamando tokenResult.TryGetToken(out var token).

Se a solicitação tiver sido bem-sucedida, a variável de token será preenchida com o


token de acesso. A AccessToken.Value propriedade do token expõe a cadeia de
caracteres literal a ser incluída no cabeçalho da solicitação Authorization .

Se a solicitação falhou porque o token não pôde ser provisionado sem interação do
usuário:

ASP.NET Core 7.0 ou posterior: o aplicativo navega para


AccessTokenResult.InteractiveRequestUrl o usando o fornecido
AccessTokenResult.InteractionOptions para permitir a atualização do token de

acesso.
ASP.NET Core 6.0 ou anterior: o resultado do token contém uma URL de
redirecionamento. Navegar até essa URL leva o usuário para a página de logon e
volta para a página atual após uma autenticação bem-sucedida.

razor

@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http
...

@code {
private WeatherForecast[] forecasts;

protected override async Task OnInitializedAsync()


{
try
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>
("WeatherForecast");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}

Executar o aplicativo
Execute o aplicativo no projeto Servidor. Ao usar o Visual Studio, também:

Defina a lista suspensa Projetos de Inicialização na barra de ferramentas para o


aplicativo de API do Servidor e selecione o botão Executar .
Selecione o projeto Servidor no Gerenciador de Soluções e selecione o botão
Executar na barra de ferramentas ou inicie o aplicativo no menu Depurar.

Solucionar problemas

Log
Para habilitar o log de depuração ou rastreamento para Blazor WebAssembly
autenticação no ASP.NET Core 7.0 ou posterior, consulte ASP.NET Core Blazor registro
em log.

Erros comuns
Configuração incorreta do aplicativo ou Identity provedor (IP)

Os erros mais comuns são causados pela configuração incorreta. A seguir, estão
alguns exemplos:
Dependendo dos requisitos do cenário, uma Autoridade ausente ou incorreta,
Instância, ID do Locatário, Domínio do Locatário, ID do Cliente ou URI de
Redirecionamento impede que um aplicativo autentique clientes.
Um escopo de token de acesso incorreto impede que os clientes acessem
pontos de extremidade da API Web do servidor.
Permissões de API de servidor incorretas ou ausentes impedem que os clientes
acessem pontos de extremidade da API Web do servidor.
Executar o aplicativo em uma porta diferente do configurado no URI de
Redirecionamento do Identity registro de aplicativo do Provedor.

As seções de configuração das diretrizes deste artigo mostram exemplos da


configuração correta. Verifique cuidadosamente cada seção do artigo em busca de
configuração incorreta de aplicativo e IP.

Se a configuração aparecer correta:

Analisar logs de aplicativo.

Examine o tráfego de rede entre o aplicativo cliente e o aplicativo IP ou servidor


com as ferramentas de desenvolvedor do navegador. Muitas vezes, uma
mensagem de erro exata ou uma mensagem com uma pista do que está
causando o problema é retornada ao cliente pelo aplicativo IP ou servidor
depois de fazer uma solicitação. Ferramentas de desenvolvedor diretrizes são
encontradas nos seguintes artigos:
Google Chrome (documentação do Google)
Microsoft Edge
Mozilla Firefox (documentação do Mozilla)

Decodificar o conteúdo de um JSToken Web ON (JWT) usado para autenticar


um cliente ou acessar uma API Web do servidor, dependendo de onde o
problema está ocorrendo. Para obter mais informações, consulte Inspecionar o
conteúdo de um JSToken Web ON (JWT).

A equipe de documentação responde a comentários de documentos e bugs em


artigos (abra um problema na seção Comentários desta página ), mas não
consegue fornecer suporte ao produto. Vários fóruns de suporte público estão
disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos
o uso do seguinte:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Os fóruns anteriores não são de propriedade ou controlados por Microsoft.


Para relatórios de bugs de estrutura reproduzível não confidenciais, não
confidenciais e não confidenciais, abra um problema com a unidade do produto
ASP.NET Core . Não abra um problema com a unidade do produto até que você
investigue minuciosamente a causa de um problema e não possa resolvê-lo por
conta própria e com a ajuda da comunidade em um fórum de suporte público. A
unidade do produto não é capaz de solucionar problemas de aplicativos
individuais que estão quebrados devido a simples configuração incorreta ou casos
de uso envolvendo serviços de terceiros. Se um relatório for confidencial ou
confidencial por natureza ou descrever uma possível falha de segurança no
produto que os invasores podem explorar, consulte Relatando problemas de
segurança e bugs (repositório GitHub dotnet/aspnetcore) .

Cliente não autorizado para o AAD

info: Microsoft. Falha na autorização de


AspNetCore.Authorization.DefaultAuthorizationService[2]. Esses requisitos não
foram atendidos: DenyAnonymousAuthorizationRequirement: requer um
usuário autenticado.

Erro de retorno de chamada de logon do AAD:


Erro: unauthorized_client
Descrição: AADB2C90058: The provided application is not configured to allow
public clients.

Para resolver o erro:

1. No portal do Azure, acesse o manifesto do aplicativo.


2. Defina o allowPublicClient atributo como null ou true .

Cookies e dados do site


Cookies e dados do site podem persistir entre as atualizações do aplicativo e interferir
no teste e na solução de problemas. Desmarque o seguinte ao fazer alterações no
código do aplicativo, alterações na conta de usuário com o provedor ou alterações na
configuração do aplicativo do provedor:

Entradas do cookieusuário
Aplicativos cookie
Dados do site armazenados e armazenados em cache

Uma abordagem para impedir que os dados persistentes cookiedo site e do site
interfiram no teste e na solução de problemas é:
Configurar um navegador
Use um navegador para testar que você pode configurar para excluir todos os
cookie dados do site e sempre que o navegador for fechado.
Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer
alteração no aplicativo, usuário de teste ou configuração do provedor.
Use um comando personalizado para abrir um navegador no modo anônimo ou
privado no Visual Studio:
Abra a caixa de diálogo Procurar com no botão Executar do Visual Studio.
Selecione o botão Adicionar.
Forneça o caminho para o navegador no campo Programa . Os seguintes
caminhos executáveis são locais de instalação típicos para Windows 10. Se o
navegador estiver instalado em um local diferente ou você não estiver usando
Windows 10, forneça o caminho para o executável do navegador.
Microsoft Edge: C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe

Google Chrome: C:\Program Files


(x86)\Google\Chrome\Application\chrome.exe
Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
No campo Argumentos , forneça a opção de linha de comando que o
navegador usa para abrir no modo anônimo ou privado. Alguns navegadores
exigem a URL do aplicativo.
Microsoft Edge: use -inprivate .
Google Chrome: use --incognito --new-window {URL} , em que o espaço
reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Mozilla Firefox: use -private -url {URL} , em que o espaço reservado {URL}
é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Forneça um nome no campo Nome amigável . Por exemplo, Firefox Auth
Testing .

Selecione o botão OK.


Para evitar a necessidade de selecionar o perfil do navegador para cada iteração
de teste com um aplicativo, defina o perfil como o padrão com o botão Definir
como Padrão .
Verifique se o navegador está fechado pelo IDE para qualquer alteração na
configuração do aplicativo, do usuário de teste ou do provedor.

Atualizações de aplicativos
Um aplicativo funcional pode falhar imediatamente após atualizar o SDK do .NET Core
no computador de desenvolvimento ou alterar as versões do pacote dentro do
aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao
executar atualizações principais. A maioria desses problemas pode ser corrigida
seguindo estas instruções:

1. Limpe os caches de pacote NuGet do sistema local executando dotnet nuget locals
all --clear de um shell de comando.
2. Exclua as pastas e obj do bin projeto.
3. Restaure e recompile o projeto.
4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar
o aplicativo.

7 Observação

Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de


destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do
NuGet ou o Gerenciador de Pacotes FuGet .

Executar o aplicativo Servidor


Ao testar e solucionar problemas de uma solução hospedadaBlazor WebAssembly,
verifique se você está executando o aplicativo no Server projeto. Por exemplo, no Visual
Studio, confirme se o projeto servidor está realçado em Gerenciador de Soluções antes
de iniciar o aplicativo com qualquer uma das seguintes abordagens:

Selecione o botão Executar.


Use Depurar>Iniciar Depuração no menu.
Pressione F5 .

Inspecionar o usuário
Os ativos de teste da estrutura ASP.NET Core incluem um Blazor WebAssembly
aplicativo cliente com um User componente que pode ser útil na solução de
problemas. O User componente pode ser usado diretamente em aplicativos ou servir
como base para personalização adicional:

User componente de teste no dotnet/aspnetcore repositório GitHub

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Inspecionar o conteúdo de um JSToken Web ON (JWT)


Para decodificar um JSToken Web ON (JWT), use a ferramenta de jwt.ms do Microsoft .
Os valores na interface do usuário nunca saem do navegador.

Exemplo de JWT codificado (abreviado para exibição):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j ...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q

Exemplo de JWT decodificado pela ferramenta para um aplicativo que se autentica no


Azure AAD B2C:

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Recursos adicionais
Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly
Criar uma versão personalizada da biblioteca JavaScript Authentication.MSAL
Solicitações de API Web não autenticadas ou não autorizadas em um aplicativo
com um cliente padrão seguro
ASP.NET Core Blazor WebAssembly com grupos e funções do Azure Active
Directory
Plataforma de identidade da Microsoft e Azure Active Directory com ASP.NET Core
Documentação da plataforma de identidade da Microsoft
Início Rápido: Registrar um aplicativo na plataforma de identidade da Microsoft
Proteger um aplicativo de ASP.NET Core
Blazor WebAssembly hospedado com o
Azure Active Directory B2C
Artigo • 21/12/2022 • 98 minutos para o fim da leitura

Este artigo explica como criar uma solução hospedada Blazor WebAssembly que usa o
AAD (Azure Active Directory) B2C para autenticação.

Para obter mais informações sobre as soluções, consulte Ferramentas para ASP.NET Core
Blazor.

Registrar aplicativos no AAD B2C e criar


solução

Criar um locatário
Siga as diretrizes em Tutorial: Criar um locatário do Azure Active Directory B2C para criar
um locatário do AAD B2C. Retorne a este artigo imediatamente após criar ou identificar
um locatário a ser usado.

Registre a instância do AAD B2C (por exemplo, https://contoso.b2clogin.com/ , que


inclui a barra à direita). A instância é o esquema e o host de um registro de aplicativo do
Azure B2C, que pode ser encontrado abrindo a janela Pontos de Extremidade na página
Registros de aplicativo no portal do Azure.

Registrar um aplicativo de API do servidor


Registre um aplicativo AAD B2C para o aplicativo de API do Servidor:

1. Navegue até Azure Active Directory no portal do Azure. Selecione Registros de


aplicativo na barra lateral. Selecione o botão Novo registro .
2. Forneça um Nome para o aplicativo (por exemplo, Blazor Server AAD B2C).
3. Para Tipos de conta com suporte, selecione a opção multilocatário: Contas em
qualquer provedor de identidade ou diretório organizacional (para autenticar
usuários com fluxos de usuário)
4. O aplicativo de API do Servidor não requer um URI de Redirecionamento nesse
cenário, portanto, deixe a lista suspensa definida como Web e não insira um URI
de redirecionamento.
5. Se você estiver usando um domínio publicador não verificado, confirme se
Permissões>Conceder consentimento do administrador para openid e
offline_access permissões estão selecionadas . Se o domínio do editor for
verificado, essa caixa de seleção não estará presente.
6. Selecione Registrar.

Registre as seguintes informações:

Aplicativo de API do Servidor ID do aplicativo (cliente) (por exemplo, 41451fa7-


82d9-4673-8fa5-69eff5a761fd )

Domínio primário/publicador/locatário do AAD (por exemplo,


contoso.onmicrosoft.com ): o domínio está disponível como o domínio do

Publicador na folha Identidade Visual do portal do Azure para o aplicativo


registrado.

Em Expor uma API:

1. Selecione Adicionar um escopo.


2. Selecione Salvar e continuar.
3. Forneça um nome de escopo (por exemplo, API.Access ).
4. Forneça um Administração nome de exibição de consentimento (por exemplo,
Access API ).

5. Forneça uma descrição de consentimento Administração (por exemplo, Allows


the app to access server app API endpoints. ).

6. Confirme se o Estado está definido como Habilitado.


7. Selecione Adicionar escopo.

Registre as seguintes informações:

URI da ID do aplicativo (por exemplo, api://41451fa7-82d9-4673-8fa5-


69eff5a761fd , https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-

69eff5a761fd ou o valor personalizado que você forneceu)


Nome do escopo (por exemplo, API.Access )

Registrar um aplicativo cliente


Registre um aplicativo AAD B2C para o aplicativo cliente:

1. Navegue até Azure Active Directory no portal do Azure. Selecione Registros de


aplicativo na barra lateral. Selecione o botão Novo registro .
2. Forneça um Nome para o aplicativo (por exemplo, Blazor Cliente AAD B2C).
3. Para Tipos de conta com suporte, selecione a opção multilocatário: Contas em
qualquer provedor de identidade ou diretório organizacional (para autenticar
usuários com fluxos de usuário)
4. Defina a lista suspensa URI de redirecionamento como SPA (aplicativo de página
única) e forneça o seguinte URI de redirecionamento:
https://localhost/authentication/login-callback . Se você souber o URI de

redirecionamento de produção para o host padrão do Azure (por exemplo,


azurewebsites.net ) ou o host de domínio personalizado (por exemplo,

contoso.com ), também poderá adicionar o URI de redirecionamento de produção


ao mesmo tempo em que estiver fornecendo o localhost URI de
redirecionamento. Certifique-se de incluir o número da porta para :443 portas não
em quaisquer URIs de redirecionamento de produção que você adicionar.
5. Se você estiver usando um domínio publicador não verificado, confirme se
Permissões>Conceder consentimento do administrador para openid e
offline_access permissões estão selecionadas . Se o domínio do editor for
verificado, essa caixa de seleção não estará presente.
6. Selecione Registrar.

7 Observação

Não é necessário fornecer o número da porta para um localhost URI de


redirecionamento do AAD B2C. Para obter mais informações, consulte Restrições e
limitações do URI de redirecionamento (URL de resposta): exceções de localhost
(documentação do Azure).

Registre a ID do aplicativo (cliente) (por exemplo, 4369008b-21fa-427c-abaa-


9b53bf58e538 ).

EmSpa (aplicativo de página única) deconfigurações> da Plataforma de


Autenticação>:

1. Confirme se o URI de Redirecionamento de


https://localhost/authentication/login-callback está presente.

2. Na seção Concessão implícita , verifique se as caixas de seleção tokens de acesso


e tokens de IDnão estão selecionadas.
3. Os padrões restantes para o aplicativo são aceitáveis para essa experiência.
4. Selecione o botão Salvar.

Em permissões de API:

1. Selecione Adicionar uma permissão seguida por Minhas APIs.


2. Selecione o aplicativo de API do Servidor na coluna Nome (por exemplo, Blazor
Server AAD B2C).
3. Abra a lista de API .
4. Habilite o acesso à API (por exemplo, API.Access ).
5. Selecione Adicionar Permissões.
6. Selecione o botão Conceder consentimento do administrador para {NOME DO
LOCATÁRIO} . Clique em Sim para confirmar.

) Importante

Se você não tiver autoridade para conceder consentimento de administrador ao


locatário na última etapa da configuração de permissões de API porque o
consentimento para usar o aplicativo é delegado aos usuários, você deve seguir as
seguintes etapas adicionais:

O aplicativo deve usar um domínio de editor confiável.


Server Na configuração do aplicativo no portal do Azure, selecione Expor
uma API. Em Aplicativos cliente autorizados, selecione o botão Para
Adicionar um aplicativo cliente. Adicione a Client ID do aplicativo (cliente)
(por exemplo, 4369008b-21fa-427c-abaa-9b53bf58e538 ).

No Home> Azure ADfluxos de usuárioB2C>:

Criar um fluxo de usuário de inscrição e de entrada

No mínimo, selecione o atributo de usuárioNome de Exibição de declarações> do


aplicativo para preencher o context.User.Identity.Name LoginDisplay no componente
( Shared/LoginDisplay.razor ).

Registre o nome de fluxo de usuário de inscrição e entrada criado para o aplicativo (por
exemplo, B2C_1_signupsignin ).

Criar o aplicativo
Substitua os espaços reservados no comando a seguir pelas informações registradas
anteriormente e execute o comando em um shell de comando:

CLI do .NET

dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C


INSTANCE}" --api-client-id "{SERVER API APP CLIENT ID}" --app-id-uri "
{SERVER API APP ID URI}" --client-id "{CLIENT APP CLIENT ID}" --default-
scope "{DEFAULT SCOPE}" --domain "{TENANT DOMAIN}" -ho -o {APP NAME} -ssp "
{SIGN UP OR SIGN IN POLICY}"

2 Aviso

Evite usar traços ( - ) no nome {APP NAME} do aplicativo que interrompem a


formação do identificador de aplicativo OIDC. A lógica no Blazor WebAssembly
modelo de projeto usa o nome do projeto para um identificador de aplicativo OIDC
na configuração da solução. Pascal case ( BlazorSample ) ou sublinhados
( Blazor_Sample ) são alternativas aceitáveis. Para obter mais informações, consulte
Traços em um nome de projeto hospedado Blazor WebAssembly interrompem a
segurança do OIDC (dotnet/aspnetcore #35337) .

Espaço reservado Nome do portal do Azure Exemplo

{AAD B2C Instância https://contoso.b2clogin.com/


INSTANCE} (inclui a barra à direita)

{APP NAME} — BlazorSample

{CLIENT APP ID do aplicativo (cliente) para o Client 4369008b-21fa-427c-abaa-


CLIENT ID} aplicativo 9b53bf58e538

{DEFAULT SCOPE} Nome do escopo API.Access

{SERVER API APP ID do aplicativo (cliente) para o 41451fa7-82d9-4673-8fa5-


CLIENT ID} aplicativo de API do Servidor 69eff5a761fd

{SERVER API APP URI da ID do aplicativo† 41451fa7-82d9-4673-8fa5-


ID URI} 69eff5a761fd †

{SIGN UP OR SIGN Fluxo de usuário de inscrição/entrada B2C_1_signupsignin1


IN POLICY}

{TENANT DOMAIN} Domínio contoso.onmicrosoft.com


primário/publicador/locatário

†O Blazor WebAssembly modelo adiciona automaticamente um esquema de api:// ao


argumento URI da ID do Aplicativo passado no dotnet new comando . Ao fornecer o
URI da ID do Aplicativo para o {SERVER API APP ID URI} espaço reservado e se o
esquema for api:// , remova o esquema ( api:// ) do argumento , como mostra o valor
de exemplo na tabela anterior. Se o URI da ID do Aplicativo for um valor personalizado
ou tiver algum outro esquema (por exemplo, https:// para um domínio de editor não
verificado semelhante a https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-
69eff5a761fd ), você deverá atualizar manualmente o URI de escopo padrão e remover o

api:// esquema depois que o Client aplicativo for criado pelo modelo. Para obter mais
informações, consulte a observação na seção Escopos do token de acesso . O Blazor
WebAssembly modelo pode ser alterado em uma versão futura do ASP.NET Core para
resolver esses cenários. Para obter mais informações, consulte Esquema duplo para o
URI da ID do Aplicativo com Blazor o modelo WASM (hospedado, organização única)
(dotnet/aspnetcore #27417) .

A localização de saída especificada com a opção -o|--output criará uma pasta de


projeto se ela não existir e se tornará parte do nome do aplicativo. Evite usar traços ( - )
no nome do aplicativo que interrompem a formação do identificador de aplicativo
OIDC (consulte o AVISO anterior).

7 Observação

O escopo configurado em uma solução hospedada Blazor WebAssembly pelo


modelo deBlazor WebAssembly projeto pode ter o host de URI da ID do
Aplicativo repetido. Confirme se o escopo configurado para a
DefaultAccessTokenScopes coleção está correto no Program.cs do Client aplicativo.

Server configuração do aplicativo


Esta seção pertence ao aplicativo da Server solução.

Pacote de autenticação
O suporte para autenticar e autorizar chamadas para ASP.NET Core APIs Web com a
plataforma Microsoft Identity é fornecido pelo Microsoft.Identity.Web pacote.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

O Server aplicativo de uma solução hospedada Blazor criada com base no Blazor
WebAssembly modelo inclui o Microsoft.Identity.Web.UI pacote por padrão. O pacote
adiciona interface do usuário para autenticação de usuário em aplicativos Web e não é
usado pela Blazor estrutura. Se o Server aplicativo não for usado para autenticar
usuários diretamente, é seguro remover a referência de pacote do arquivo de Server
projeto do aplicativo.

Suporte ao serviço de autenticação


O AddAuthentication método configura os serviços de autenticação no aplicativo e
configura o manipulador de Portador JWT como o método de autenticação padrão. O
AddMicrosoftIdentityWebApi método configura serviços para proteger a API Web com
Microsoft Identity Platform v2.0. Esse método espera uma AzureAdB2C seção na
configuração do aplicativo com as configurações necessárias para inicializar as opções
de autenticação.

C#

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C"));

7 Observação

Quando um único esquema de autenticação é registrado, o esquema de


autenticação é usado automaticamente como o esquema padrão do aplicativo e
não é necessário declarar o esquema para AddAuthentication ou por meio
AuthenticationOptionsde . Para obter mais informações, consulte Visão geral da
Autenticação ASP.NET Core e o comunicado do ASP.NET Core
(aspnet/Announcements #490) .

UseAuthentication e UseAuthorization certifique-se de que:

O aplicativo tenta analisar e validar tokens em solicitações de entrada.


Qualquer solicitação que tente acessar um recurso protegido sem credenciais
adequadas falhará.

C#

app.UseAuthentication();
app.UseAuthorization();

User.Identity. Nome
Por padrão, o User.Identity.Name não é preenchido.
Para configurar o aplicativo para receber o valor do tipo de name declaração:

Adicione um namespace para Microsoft.AspNetCore.Authentication.JwtBearer a


Program.cs :

C#

using Microsoft.AspNetCore.Authentication.JwtBearer;

Configure o TokenValidationParameters.NameClaimType do JwtBearerOptions em


Program.cs :

C#

builder.Services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.NameClaimType = "name";
});

Configurações de aplicativo
O appsettings.json arquivo contém as opções para configurar o manipulador de
portador JWT usado para validar tokens de acesso.

JSON

{
"AzureAdB2C": {
"Instance": "https://{TENANT}.b2clogin.com/",
"ClientId": "{SERVER API APP CLIENT ID}",
"Domain": "{TENANT DOMAIN}",
"SignUpSignInPolicyId": "{SIGN UP OR SIGN IN POLICY}"
}
}

Exemplo:

JSON

{
"AzureAdB2C": {
"Instance": "https://contoso.b2clogin.com/",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"Domain": "contoso.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_signupsignin1",
}
}

Controlador WeatherForecast
O controlador WeatherForecast ( Controllers/WeatherForecastController.cs ) expõe uma
API protegida com o [Authorize] atributo aplicado ao controlador. É importante
entender que:

O [Authorize] atributo nesse controlador de API é a única coisa que protege essa
API contra acesso não autorizado.
O [Authorize] atributo usado no Blazor WebAssembly aplicativo serve apenas
como uma dica para o aplicativo de que o usuário deve ser autorizado para que o
aplicativo funcione corretamente.

C#

[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
...
}
}

Client configuração do aplicativo


Esta seção pertence ao aplicativo da Client solução.

Pacote de autenticação
Quando um aplicativo é criado para usar uma Conta B2C Individual ( IndividualB2C ), o
aplicativo recebe automaticamente uma referência de pacote para a Biblioteca de
Autenticação do Microsoft (Microsoft.Authentication.WebAssembly.Msal ). O pacote
fornece um conjunto de primitivos que ajudam o aplicativo a autenticar usuários e obter
tokens para chamar APIs protegidas.

Se adicionar autenticação a um aplicativo, adicione manualmente o


Microsoft.Authentication.WebAssembly.Msal pacote ao aplicativo.
7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

O Microsoft.Authentication.WebAssembly.Msal pacote adiciona transitivamente o


Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote ao
aplicativo.

Suporte ao serviço de autenticação


É adicionado suporte para HttpClient instâncias que incluem tokens de acesso ao fazer
solicitações ao projeto do servidor.

Program.cs :

C#

builder.Services.AddHttpClient("{APP ASSEMBLY}.ServerAPI", client =>


client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()


.CreateClient("{APP ASSEMBLY}.ServerAPI"));

O espaço reservado {APP ASSEMBLY} é o nome do assembly do aplicativo (por exemplo,


BlazorSample.Client ).

O suporte para autenticação de usuários é registrado no contêiner de serviço com o


AddMsalAuthentication método de extensão fornecido pelo
Microsoft.Authentication.WebAssembly.Msal pacote. Esse método configura os
serviços necessários para que o aplicativo interaja com o Identity PROVEDOR (IP).

Program.cs :

C#

builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C",
options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

O AddMsalAuthentication método aceita um retorno de chamada para configurar os


parâmetros necessários para autenticar um aplicativo. Os valores necessários para
configurar o aplicativo podem ser obtidos na configuração do AAD do Portal do Azure
quando você registra o aplicativo.

A configuração é fornecida pelo wwwroot/appsettings.json arquivo :

JSON

{
"AzureAdB2C": {
"Authority": "{AAD B2C INSTANCE}{TENANT DOMAIN}/{SIGN UP OR SIGN IN
POLICY}",
"ClientId": "{CLIENT APP CLIENT ID}",
"ValidateAuthority": false
}
}

Na configuração anterior, o {AAD B2C INSTANCE} inclui uma barra à direita.

Exemplo:

JSON

{
"AzureAdB2C": {
"Authority":
"https://contoso.b2clogin.com/contoso.onmicrosoft.com/B2C_1_signupsignin1",
"ClientId": "4369008b-21fa-427c-abaa-9b53bf58e538",
"ValidateAuthority": false
}
}

Escopos de token de acesso


Os escopos de token de acesso padrão representam a lista de escopos de token de
acesso que são:

Incluído por padrão na solicitação de entrada.


Usado para provisionar um token de acesso imediatamente após a autenticação.

Todos os escopos devem pertencer ao mesmo aplicativo por regras do Azure Active
Directory. Escopos adicionais podem ser adicionados para aplicativos de API adicionais,
conforme necessário:

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

7 Observação

O Blazor WebAssembly modelo adiciona automaticamente um esquema de api://


ao argumento URI da ID do Aplicativo passado no dotnet new comando . Ao gerar
um aplicativo do Blazor modelo de projeto, confirme se o valor do escopo do
token de acesso padrão usa o valor de URI de ID do Aplicativo personalizado
correto fornecido no portal do Azure ou um valor com um dos seguintes formatos:

Quando o domínio do editor do diretório é confiável, o escopo do token de


acesso padrão normalmente é um valor semelhante ao exemplo a seguir, em
API.Access que é o nome de escopo padrão:

C#

options.ProviderOptions.DefaultAccessTokenScopes.Add(
"api://41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");

Inspecione o valor de um esquema duplo ( api://api://... ). Se um esquema


duplo estiver presente, remova o primeiro api:// esquema do valor.

Quando o domínio do editor do diretório não é confiável, o escopo do token


de acesso padrão normalmente é um valor semelhante ao exemplo a seguir,
em API.Access que é o nome de escopo padrão:

C#

options.ProviderOptions.DefaultAccessTokenScopes.Add(
"https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-
69eff5a761fd/API.Access");

Inspecione o valor de um esquema extra api://


( api://https://contoso.onmicrosoft.com/... ). Se houver um esquema extra
api:// , remova o api:// esquema do valor .
O Blazor WebAssembly modelo pode ser alterado em uma versão futura do
ASP.NET Core para resolver esses cenários. Para obter mais informações, consulte
Esquema duplo para o URI da ID do Aplicativo com Blazor o modelo WASM
(hospedado, organização única) (dotnet/aspnetcore #27417) .

Especifique escopos adicionais com AdditionalScopesToConsent :

C#

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");

Para obter mais informações, consulte as seguintes seções do artigo Cenários adicionais
:

Solicitar tokens de acesso adicionais


Anexar tokens a solicitações de saída

Modo de logon
A estrutura usa como padrão o modo de logon pop-up e volta para o modo de logon
de redirecionamento se um pop-up não puder ser aberto. Configure a MSAL para usar o
modo de logon de redirecionamento definindo a LoginMode propriedade de
MsalProviderOptions como redirect :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.LoginMode = "redirect";
});

A configuração padrão é popup e o valor da cadeia de caracteres não diferencia


maiúsculas de minúsculas.

Importa o arquivo
O Microsoft.AspNetCore.Components.Authorization namespace é disponibilizado em
todo o aplicativo por meio do _Imports.razor arquivo :

razor
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}.Client
@using {APPLICATION ASSEMBLY}.Client.Shared

Página de índice
A página Índice ( wwwroot/index.html ) inclui um script que define o
AuthenticationService em JavaScript. AuthenticationService manipula os detalhes de

baixo nível do protocolo OIDC. O aplicativo chama internamente métodos definidos no


script para executar as operações de autenticação.

HTML

<script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>

Componente do aplicativo
O App componente ( App.razor ) é semelhante ao App componente encontrado em
Blazor Server aplicativos:

O CascadingAuthenticationState componente gerencia a exposição do


AuthenticationState ao restante do aplicativo.
O AuthorizeRouteView componente garante que o usuário atual esteja autorizado
a acessar uma determinada página ou renderize o RedirectToLogin componente
de outra forma.
O RedirectToLogin componente gerencia o redirecionamento de usuários não
autorizados para a página de logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


App componente ( App.razor ) não é mostrada nesta seção. Para inspecionar a marcação
do componente para uma determinada versão, use uma das seguintes abordagens:
Crie um aplicativo provisionado para autenticação do modelo de projeto padrão
Blazor WebAssembly para a versão do ASP.NET Core que você pretende usar.
Inspecione o App componente ( App.razor ) no aplicativo gerado.

Inspecione o App componente ( App.razor ) na fonte de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente RedirectToLogin
O RedirectToLogin componente ( Shared/RedirectToLogin.razor ):

Gerencia o redirecionamento de usuários não autorizados para a página de logon.


Preserva a URL atual que o usuário está tentando acessar para que ele possa ser
retornado a essa página se a autenticação for bem-sucedida.

razor

@inject NavigationManager Navigation


@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions
>> Options
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin(Options.Get(
Microsoft.Extensions.Options.Options.DefaultName)
.AuthenticationPaths.LogInPath);
}
}

Componente LoginDisplay
O LoginDisplay componente ( Shared/LoginDisplay.razor ) é renderizado no MainLayout
componente ( Shared/MainLayout.razor ) e gerencia os seguintes comportamentos:

Para usuários autenticados:


Exibe o nome de usuário atual.
Oferece um botão para fazer logoff do aplicativo.
Para usuários anônimos, oferece a opção de fazer logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


LoginDisplay componente não é mostrada nesta seção. Para inspecionar a marcação do

componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão do ASP.NET Core que você pretende usar.
Inspecione o LoginDisplay componente no aplicativo gerado.

Inspecione o LoginDisplay componente na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente de autenticação
A página produzida pelo Authentication componente ( Pages/Authentication.razor )
define as rotas necessárias para lidar com diferentes estágios de autenticação.

O RemoteAuthenticatorView componente:

É fornecido pelo
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote.
Gerencia a execução das ações apropriadas em cada estágio de autenticação.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />

@code {
[Parameter]
public string Action { get; set; }
}

Componente FetchData
O FetchData componente mostra como:

Provisionar um token de acesso.


Use o token de acesso para chamar uma API de recurso protegido no aplicativo
Servidor .

A @attribute [Authorize] diretiva indica ao Blazor WebAssembly sistema de autorização


que o usuário deve estar autorizado para visitar esse componente. A presença do
atributo no Client aplicativo não impede que a API no servidor seja chamada sem as
credenciais adequadas. O Server aplicativo também deve usar [Authorize] nos pontos
de extremidade apropriados para protegê-los corretamente.

IAccessTokenProvider.RequestAccessToken cuida da solicitação de um token de acesso


que pode ser adicionado à solicitação para chamar a API. Se o token for armazenado em
cache ou se o serviço puder provisionar um novo token de acesso sem interação do
usuário, a solicitação de token será bem-sucedida. Caso contrário, a solicitação de token
falhará com um AccessTokenNotAvailableException, que é capturado em uma try-catch
instrução .

Para obter o token real a ser incluído na solicitação, o aplicativo deve verificar se a
solicitação foi bem-sucedida chamando tokenResult.TryGetToken(out var token).

Se a solicitação tiver sido bem-sucedida, a variável de token será preenchida com o


token de acesso. A AccessToken.Value propriedade do token expõe a cadeia de
caracteres literal a ser incluída no cabeçalho da solicitação Authorization .

Se a solicitação falhou porque o token não pôde ser provisionado sem interação do
usuário:

ASP.NET Core 7.0 ou posterior: o aplicativo navega até


AccessTokenResult.InteractiveRequestUrl usando o fornecido

AccessTokenResult.InteractionOptions para permitir a atualização do token de

acesso.
ASP.NET Core 6.0 ou anterior: o resultado do token contém uma URL de
redirecionamento. Navegar até essa URL leva o usuário para a página de logon e
volta para a página atual após uma autenticação bem-sucedida.

razor

@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
private WeatherForecast[] forecasts;

protected override async Task OnInitializedAsync()


{
try
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>
("WeatherForecast");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}

Executar o aplicativo
Execute o aplicativo no projeto Servidor. Ao usar o Visual Studio, :

Defina a lista suspensa Projetos de Inicialização na barra de ferramentas para o


aplicativo de API do Servidor e selecione o botão Executar .
Selecione o projeto Servidor no Gerenciador de Soluções e selecione o botão
Executar na barra de ferramentas ou inicie o aplicativo no menu Depurar.

Fluxos de usuário personalizados


A Biblioteca de Autenticação do Microsoft (Microsoft.Authentication.WebAssembly.Msal,
pacote NuGet ) não dá suporte a fluxos de usuário do AAD B2C por padrão. Crie fluxos
de usuário personalizados no código do desenvolvedor.

Para obter mais informações sobre como criar um desafio para um fluxo de usuário
personalizado, consulte Fluxos de usuário no Azure Active Directory B2C.
Solucionar problemas

Log
Para habilitar o log de depuração ou rastreamento para Blazor WebAssembly
autenticação no ASP.NET Core 7.0 ou posterior, consulte ASP.NET Core Blazor registro
em log.

Erros comuns
Configuração incorreta do aplicativo ou Identity provedor (IP)

Os erros mais comuns são causados pela configuração incorreta. A seguir, estão
alguns exemplos:
Dependendo dos requisitos do cenário, uma Autoridade, Instância, ID do
Locatário, Domínio do Locatário, ID do Cliente ou URI de Redirecionamento
ausente ou incorreto impede que um aplicativo autentique clientes.
Um escopo de token de acesso incorreto impede que os clientes acessem
pontos de extremidade da API Web do servidor.
Permissões incorretas ou ausentes da API do servidor impedem que os clientes
acessem pontos de extremidade da API Web do servidor.
A execução do aplicativo em uma porta diferente da está configurada no URI de
Redirecionamento do Identity registro de aplicativo do Provedor.

As seções de configuração das diretrizes deste artigo mostram exemplos da


configuração correta. Verifique cuidadosamente cada seção do artigo em busca de
configuração incorreta de aplicativo e IP.

Se a configuração parecer correta:

Analisar logs do aplicativo.

Examine o tráfego de rede entre o aplicativo cliente e o aplicativo IP ou servidor


com as ferramentas de desenvolvedor do navegador. Geralmente, uma
mensagem de erro exata ou uma mensagem com uma pista do que está
causando o problema é retornada ao cliente pelo aplicativo IP ou servidor
depois de fazer uma solicitação. Ferramentas de desenvolvedor diretrizes são
encontradas nos seguintes artigos:
Google Chrome (documentação do Google)
Microsoft Edge
Mozilla Firefox (documentação do Mozilla)
Decodificar o conteúdo de um JSToken Web ON (JWT) usado para autenticar
um cliente ou acessar uma API Web do servidor, dependendo de onde o
problema está ocorrendo. Para obter mais informações, consulte Inspecionar o
conteúdo de um JSToken Web ON (JWT).

A equipe de documentação responde a comentários de documentos e bugs em


artigos (abra um problema na seção Comentários desta página ), mas não
consegue fornecer suporte ao produto. Vários fóruns de suporte público estão
disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos
o uso do seguinte:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Os fóruns anteriores não são de propriedade ou controlados por Microsoft.

Para relatórios de bugs de estrutura não confidenciais, não confidenciais e não


confidenciais, abra um problema com a unidade de produto ASP.NET Core . Não
abra um problema com a unidade de produto até que você tenha investigado
minuciosamente a causa de um problema e não possa resolvê-lo por conta própria
e com a ajuda da comunidade em um fórum de suporte público. A unidade de
produto não é capaz de solucionar problemas de aplicativos individuais que foram
interrompidos devido a simples erros de configuração ou casos de uso envolvendo
serviços de terceiros. Se um relatório for confidencial ou confidencial por natureza
ou descrever uma possível falha de segurança no produto que os invasores podem
explorar, consulte Relatando problemas de segurança e bugs (repositório GitHub
dotnet/aspnetcore) .

Cliente não autorizado para o AAD

info: Microsoft. Falha na autorização de


AspNetCore.Authorization.DefaultAuthorizationService[2]. Esses requisitos não
foram atendidos: DenyAnonymousAuthorizationRequirement: requer um
usuário autenticado.

Erro de retorno de chamada de logon do AAD:


Erro: unauthorized_client
Descrição: AADB2C90058: The provided application is not configured to allow
public clients.

Para resolver o erro:

1. No portal do Azure, acesse o manifesto do aplicativo.


2. Defina o allowPublicClient atributo como null ou true .

Cookies e dados do site


Cookieos dados do site e do s podem persistir entre atualizações de aplicativo e
interferir no teste e na solução de problemas. Desmarque o seguinte ao fazer alterações
no código do aplicativo, alterações na conta de usuário com o provedor ou alterações
na configuração do aplicativo do provedor:

Entradas do cookieusuário
Aplicativos cookie
Dados do site armazenados e armazenados em cache

Uma abordagem para impedir que os dados persistentes cookiedo site e do site
interfiram no teste e na solução de problemas é:

Configurar um navegador
Use um navegador para testar que você pode configurar para excluir todos os
cookie dados do site e sempre que o navegador for fechado.
Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer
alteração no aplicativo, usuário de teste ou configuração do provedor.
Use um comando personalizado para abrir um navegador no modo anônimo ou
privado no Visual Studio:
Abra a caixa de diálogo Procurar com no botão Executar do Visual Studio.
Selecione o botão Adicionar.
Forneça o caminho para o navegador no campo Programa . Os seguintes
caminhos executáveis são locais de instalação típicos para Windows 10. Se o
navegador estiver instalado em um local diferente ou você não estiver usando
Windows 10, forneça o caminho para o executável do navegador.
Microsoft Edge: C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe

Google Chrome: C:\Program Files


(x86)\Google\Chrome\Application\chrome.exe

Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe


No campo Argumentos , forneça a opção de linha de comando que o
navegador usa para abrir no modo anônimo ou privado. Alguns navegadores
exigem a URL do aplicativo.
Microsoft Edge: use -inprivate .
Google Chrome: use --incognito --new-window {URL} , em que o espaço
reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Mozilla Firefox: use -private -url {URL} , em que o espaço reservado {URL}
é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Forneça um nome no campo Nome amigável . Por exemplo, Firefox Auth
Testing .
Selecione o botão OK.
Para evitar a necessidade de selecionar o perfil do navegador para cada iteração
de teste com um aplicativo, defina o perfil como o padrão com o botão Definir
como Padrão .
Verifique se o navegador está fechado pelo IDE para qualquer alteração na
configuração do aplicativo, do usuário de teste ou do provedor.

Atualizações de aplicativos
Um aplicativo funcional pode falhar imediatamente após atualizar o SDK do .NET Core
no computador de desenvolvimento ou alterar as versões do pacote dentro do
aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao
executar atualizações principais. A maioria desses problemas pode ser corrigida
seguindo estas instruções:

1. Limpe os caches de pacote NuGet do sistema local executando dotnet nuget locals
all --clear de um shell de comando.
2. Exclua as pastas e obj do bin projeto.
3. Restaure e recompile o projeto.
4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar
o aplicativo.

7 Observação

Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de


destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do
NuGet ou o Gerenciador de Pacotes FuGet .

Executar o aplicativo Servidor


Ao testar e solucionar problemas de uma solução hospedadaBlazor WebAssembly,
verifique se você está executando o aplicativo no Server projeto. Por exemplo, no Visual
Studio, confirme se o projeto servidor está realçado em Gerenciador de Soluções antes
de iniciar o aplicativo com qualquer uma das seguintes abordagens:

Selecione o botão Executar.


Use Depurar>Iniciar Depuração no menu.
Pressione F5 .

Inspecionar o usuário
Os ativos de teste da estrutura ASP.NET Core incluem um Blazor WebAssembly
aplicativo cliente com um User componente que pode ser útil na solução de
problemas. O User componente pode ser usado diretamente em aplicativos ou servir
como base para personalização adicional:

User componente de teste no dotnet/aspnetcore repositório GitHub

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Inspecionar o conteúdo de um JSToken Web ON (JWT)


Para decodificar um JSToken Web ON (JWT), use a ferramenta de jwt.ms do Microsoft .
Os valores na interface do usuário nunca saem do navegador.

Exemplo de JWT codificado (abreviado para exibição):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j ...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q

Exemplo de JWT decodificado pela ferramenta para um aplicativo que se autentica no


Azure AAD B2C:

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Recursos adicionais
Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly
Criar uma versão personalizada da biblioteca JavaScript Authentication.MSAL
Solicitações de API Web não autenticadas ou não autorizadas em um aplicativo
com um cliente padrão seguro
Autenticação de nuvem com o Azure Active Directory B2C no ASP.NET Core
Tutorial: Criar um locatário do Azure Active Directory B2C
Tutorial: Registrar um aplicativo no Azure Active Directory B2C
Documentação da plataforma de identidade da Microsoft
Proteger um aplicativo de ASP.NET Core
Blazor WebAssembly hospedado com o
Identity Servidor
Artigo • 21/12/2022 • 129 minutos para o fim da leitura

Este artigo explica como criar uma solução hospedada Blazor WebAssembly que usa o
Servidor Duende Identity para autenticar usuários e chamadas à API.

Para obter mais informações sobre as soluções, consulte Ferramentas para ASP.NET Core
Blazor.

) Importante

O Software Duende pode exigir que você pague uma taxa de licença pelo uso de
produção do Duende Identity Server. Para obter mais informações, consulte Migrar
do ASP.NET Core 5.0 para o 6.0.

7 Observação

Para configurar um aplicativo autônomo ou hospedado Blazor WebAssembly para


usar uma instância de Servidor externa Identity existente, siga as diretrizes em
Proteger um aplicativo autônomo ASP.NET Core Blazor WebAssembly com a
biblioteca de Autenticação.

Visual Studio

Para criar um novo Blazor WebAssembly projeto com um mecanismo de


autenticação:

1. Criar um novo projeto.

2. Escolha o Modelo deBlazor WebAssembly aplicativo. Selecione Avançar.

3. Forneça um nome de projeto sem usar traços (consulte o seguinte AVISO).


Confirme se o Local está correto. Selecione Avançar.

2 Aviso
Evite usar traços ( - ) no nome do projeto que interrompem a formação
do identificador de aplicativo OIDC. A lógica no Blazor WebAssembly
modelo de projeto usa o nome do projeto para um identificador de
aplicativo OIDC na configuração da solução. Caso Pascal ( BlazorSample )
ou sublinhados ( Blazor_Sample ) são alternativas aceitáveis. Para obter
mais informações, consulte Traços em um nome de projeto hospedado
Blazor WebAssembly interrompem a segurança do OIDC
(dotnet/aspnetcore #35337) .

4. Na caixa de diálogo Informações adicionais, selecione Contas Individuais


como o Tipo de Autenticação para armazenar usuários dentro do aplicativo
usando o sistema do Identity ASP.NET Core.

5. Marque a caixa de seleção ASP.NET Core hospedada.

Server configuração do aplicativo


As seções a seguir descrevem adições ao projeto quando o suporte à autenticação é
incluído.

Classe de inicialização
A Startup classe tem as seguintes adições.

Em Program.cs :

IdentityASP.NET Core :

C#

builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();

IdentityServidor com um método auxiliar adicional AddApiAuthorization que


configura as convenções de ASP.NET Core padrão na parte superior do
IdentityServidor:
C#

builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

Autenticação com um método auxiliar adicional AddIdentityServerJwt que


configura o aplicativo para validar tokens JWT produzidos pelo IdentityServidor:

C#

builder.Services.AddAuthentication()
.AddIdentityServerJwt();

7 Observação

Quando um único esquema de autenticação é registrado, o esquema de


autenticação é usado automaticamente como o esquema padrão do
aplicativo e não é necessário declarar o esquema para AddAuthentication
ou por meio AuthenticationOptionsde . Para obter mais informações,
consulte Visão geral da Autenticação ASP.NET Core e o comunicado de
ASP.NET Core (aspnet/Announcements #490) .

Em Program.cs :

O Identitymiddleware do servidor expõe os pontos de extremidade do OIDC


(OpenID Connect):

C#

app.UseIdentityServer();

O middleware de autenticação é responsável por validar as credenciais de


solicitação e definir o usuário no contexto de solicitação:

C#

app.UseAuthentication();

O Middleware de Autorização permite recursos de autorização:

C#

app.UseAuthorization();
Serviço de Aplicativo do Azure no Linux
Especifique o emissor explicitamente ao implantar em Serviço de Aplicativo do Azure no
Linux. Para obter mais informações, consulte Introdução à autenticação para Aplicativos
de Página Única no ASP.NET Core.

AddApiAuthorization
O AddApiAuthorization método auxiliar configura oIdentity Servidor para cenários de
ASP.NET Core. Identity O servidor é uma estrutura poderosa e extensível para lidar com
preocupações de segurança do aplicativo. IdentityO servidor expõe complexidade
desnecessária para os cenários mais comuns. Consequentemente, um conjunto de
convenções e opções de configuração é fornecido que consideramos um bom ponto de
partida. Depois que a autenticação precisar ser alterada, o poder total do
IdentityServidor estará disponível para personalizar a autenticação para atender aos
requisitos de um aplicativo.

AdicionarIdentityServerJwt
O AddIdentityServerJwt método auxiliar configura um esquema de política para o
aplicativo como o manipulador de autenticação padrão. A política é configurada para
permitir manipular Identity todas as solicitações roteadas para qualquer subcaminho no
espaço /Identity de Identity URL . O JwtBearerHandler manipula todas as outras
solicitações. Além disso, este método:

Registra um {APPLICATION NAME}API recurso de API com Identityo Servidor com um


escopo padrão de {APPLICATION NAME}API .
Configura o middleware de token de portador JWT para validar os tokens emitidos
pelo IdentityServer para o aplicativo.

WeatherForecastController
WeatherForecastController No ( Controllers/WeatherForecastController.cs ), o
[Authorize] atributo é aplicado à classe . O atributo indica que o usuário deve ser
autorizado com base na política padrão para acessar o recurso. A política de autorização
padrão é configurada para usar o esquema de autenticação padrão, que é configurado
por AddIdentityServerJwt. O método auxiliar é configurado JwtBearerHandler como o
manipulador padrão para solicitações para o aplicativo.
ApplicationDbContext
ApplicationDbContext No ( Data/ApplicationDbContext.cs ), DbContext estende-se
ApiAuthorizationDbContext<TUser> para incluir o esquema para IdentityServidor.
ApiAuthorizationDbContext<TUser> é derivado de IdentityDbContext.

Para obter controle total do esquema de banco de dados, herda de uma das classes
disponíveis IdentityDbContext e configure o contexto para incluir o Identity esquema
chamando builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value)
no OnModelCreating método .

OidcConfigurationController
OidcConfigurationController No ( Controllers/OidcConfigurationController.cs ), o

ponto de extremidade do cliente é provisionado para atender aos parâmetros OIDC.

Configurações de aplicativo
No arquivo de configurações do aplicativo ( appsettings.json ) na raiz do projeto, a
IdentityServer seção descreve a lista de clientes configurados. No exemplo a seguir, há
um único cliente. O nome do cliente corresponde ao nome do aplicativo e é mapeado
por convenção para o parâmetro OAuth ClientId . O perfil indica o tipo de aplicativo
que está sendo configurado. O perfil é usado internamente para conduzir convenções
que simplificam o processo de configuração para o servidor.

JSON

"IdentityServer": {
"Clients": {
"{APP ASSEMBLY}.Client": {
"Profile": "IdentityServerSPA"
}
}
}

O espaço reservado {APP ASSEMBLY} é o nome do assembly do aplicativo (por exemplo,


BlazorSample.Client ).

Client configuração do aplicativo

Pacote de autenticação
Quando um aplicativo é criado para usar contas de usuário individuais ( Individual ), o
aplicativo recebe automaticamente uma referência de pacote para o
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote. O pacote
fornece um conjunto de primitivos que ajudam o aplicativo a autenticar usuários e obter
tokens para chamar APIs protegidas.

Se adicionar autenticação a um aplicativo, adicione manualmente o


Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote ao
aplicativo.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Configuração de HttpClient
No Program.cs , um nomeado HttpClient ( {APP ASSEMBLY}.ServerAPI ) é configurado para
fornecer HttpClient instâncias que incluem tokens de acesso ao fazer solicitações para a
API do servidor:

C#

builder.Services.AddHttpClient("{APP ASSEMBLY}.ServerAPI",
client => client.BaseAddress = new
Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()


.CreateClient("{APP ASSEMBLY}.ServerAPI"));

O espaço reservado {APP ASSEMBLY} é o nome do assembly do aplicativo (por exemplo,


BlazorSample.Client ).

7 Observação

Se você estiver configurando um Blazor WebAssembly aplicativo para usar uma


instância do Servidor existente Identity que não faz parte de uma solução
hospedada Blazor , altere o HttpClient registro de endereço base de
IWebAssemblyHostEnvironment.BaseAddress
( builder.HostEnvironment.BaseAddress ) para a URL do ponto de extremidade de
autorização de API do aplicativo servidor.

Suporte à autorização de API


O suporte para autenticar usuários é conectado ao contêiner de serviço pelo método de
extensão fornecido dentro do
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote. Esse
método configura os serviços exigidos pelo aplicativo para interagir com o sistema de
autorização existente.

C#

builder.Services.AddApiAuthorization();

Por padrão, a configuração do aplicativo é carregada por convenção de


_configuration/{client-id} . Por convenção, a ID do cliente é definida como o nome do
assembly do aplicativo. Essa URL pode ser alterada para apontar para um ponto de
extremidade separado chamando a sobrecarga com opções.

Importa o arquivo
O Microsoft.AspNetCore.Components.Authorization namespace é disponibilizado em
todo o aplicativo por meio do _Imports.razor arquivo:

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}.Client
@using {APPLICATION ASSEMBLY}.Client.Shared

Página de índice
A página Índice ( wwwroot/index.html ) inclui um script que define o
AuthenticationService em JavaScript. AuthenticationService manipula os detalhes de
baixo nível do protocolo OIDC. O aplicativo chama internamente métodos definidos no
script para executar as operações de autenticação.

HTML

<script
src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/Aut
henticationService.js"></script>

Componente do aplicativo
O App componente ( App.razor ) é semelhante ao App componente encontrado em
Blazor Server aplicativos:

O CascadingAuthenticationState componente gerencia a exposição do


AuthenticationState ao restante do aplicativo.
O AuthorizeRouteView componente garante que o usuário atual esteja autorizado
a acessar uma determinada página ou renderize o RedirectToLogin componente.
O RedirectToLogin componente gerencia o redirecionamento de usuários não
autorizados para a página de logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação para o


App componente ( App.razor ) não é mostrada nesta seção. Para inspecionar a marcação
do componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o App componente ( App.razor ) no aplicativo gerado.

Inspecione o App componente ( App.razor ) na origem de referência .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .
Componente RedirectToLogin
O RedirectToLogin componente ( Shared/RedirectToLogin.razor ):

Gerencia o redirecionamento de usuários não autorizados para a página de logon.


Preserva a URL atual que o usuário está tentando acessar para que ele possa ser
retornado a essa página se a autenticação for bem-sucedida.

razor

@inject NavigationManager Navigation


@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions
>> Options
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin(Options.Get(
Microsoft.Extensions.Options.Options.DefaultName)
.AuthenticationPaths.LogInPath);
}
}

Componente LoginDisplay
O LoginDisplay componente ( Shared/LoginDisplay.razor ) é renderizado no MainLayout
componente ( Shared/MainLayout.razor ) e gerencia os seguintes comportamentos:

Para usuários autenticados:


Exibe o nome de usuário atual.
Oferece um link para a página de perfil do usuário no ASP.NET Core Identity.
Oferece um botão para sair do aplicativo.
Para usuários anônimos:
Oferece a opção de se registrar.
Oferece a opção de fazer logon.

Devido a alterações na estrutura em versões de ASP.NET Core, Razor a marcação do


LoginDisplay componente não é mostrada nesta seção. Para inspecionar a marcação do
componente para uma determinada versão, use uma das seguintes abordagens:

Crie um aplicativo provisionado para autenticação do modelo de projeto padrão


Blazor WebAssembly para a versão de ASP.NET Core que você pretende usar.
Inspecione o LoginDisplay componente no aplicativo gerado.

Inspecione o LoginDisplay componente na origem de referência . O conteúdo


modelo para Hosted igual a true é usado.

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Componente de autenticação
A página produzida pelo Authentication componente ( Pages/Authentication.razor )
define as rotas necessárias para lidar com diferentes estágios de autenticação.

O RemoteAuthenticatorView componente:

É fornecido pelo
Microsoft.AspNetCore.Components.WebAssembly.Authentication pacote.
Gerencia a execução das ações apropriadas em cada estágio de autenticação.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code {
[Parameter]
public string Action { get; set; }
}

Componente FetchData
O FetchData componente mostra como:

Provisionar um token de acesso.


Use o token de acesso para chamar uma API de recurso protegido no aplicativo
Servidor .

A @attribute [Authorize] diretiva indica ao Blazor WebAssembly sistema de autorização


que o usuário deve ser autorizado para visitar esse componente. A presença do atributo
no Client aplicativo não impede que a API no servidor seja chamada sem credenciais
adequadas. O Server aplicativo também deve usar [Authorize] nos pontos de
extremidade apropriados para protegê-los corretamente.

IAccessTokenProvider.RequestAccessToken cuida da solicitação de um token de acesso


que pode ser adicionado à solicitação para chamar a API. Se o token for armazenado em
cache ou se o serviço puder provisionar um novo token de acesso sem interação do
usuário, a solicitação de token terá êxito. Caso contrário, a solicitação de token falhará
com um AccessTokenNotAvailableException, que é capturado em uma try-catch
instrução .

Para obter o token real a ser incluído na solicitação, o aplicativo deve verificar se a
solicitação foi bem-sucedida chamando tokenResult.TryGetToken(out var token).

Se a solicitação tiver sido bem-sucedida, a variável de token será preenchida com o


token de acesso. A AccessToken.Value propriedade do token expõe a cadeia de
caracteres literal a ser incluída no cabeçalho da solicitação Authorization .

Se a solicitação falhou porque o token não pôde ser provisionado sem interação do
usuário:

ASP.NET Core 7.0 ou posterior: o aplicativo navega para


AccessTokenResult.InteractiveRequestUrl o usando o fornecido

AccessTokenResult.InteractionOptions para permitir a atualização do token de

acesso.
ASP.NET Core 6.0 ou anterior: o resultado do token contém uma URL de
redirecionamento. Navegar até essa URL leva o usuário para a página de logon e
volta para a página atual após uma autenticação bem-sucedida.

razor

@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
private WeatherForecast[] forecasts;

protected override async Task OnInitializedAsync()


{
try
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>
("WeatherForecast");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}

Executar o aplicativo
Execute o aplicativo no projeto Servidor. Ao usar o Visual Studio, também:

Defina a lista suspensa Projetos de Inicialização na barra de ferramentas para o


aplicativo de API do Servidor e selecione o botão Executar .
Selecione o projeto Servidor no Gerenciador de Soluções e selecione o botão
Executar na barra de ferramentas ou inicie o aplicativo no menu Depurar.

Declaração de nome e função com autorização


de API

Fábrica de usuários personalizada


Client No aplicativo, crie uma fábrica de usuários personalizada. Identity O servidor
envia várias funções como uma JSmatriz ON em uma única role declaração. Uma única
função é enviada como um valor de cadeia de caracteres na declaração. A fábrica cria
uma declaração individual role para cada uma das funções do usuário.

CustomUserFactory.cs :

C#

using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
public class CustomUserFactory
: AccountClaimsPrincipalFactory<RemoteUserAccount>
{
public CustomUserFactory(IAccessTokenProviderAccessor accessor)
: base(accessor)
{
}

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(


RemoteUserAccount account,
RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);

if (user.Identity.IsAuthenticated)
{
var identity = (ClaimsIdentity)user.Identity;
var roleClaims =
identity.FindAll(identity.RoleClaimType).ToArray();

if (roleClaims.Any())
{
foreach (var existingClaim in roleClaims)
{
identity.RemoveClaim(existingClaim);
}

var rolesElem =
account.AdditionalProperties[identity.RoleClaimType];

if (rolesElem is JsonElement roles)


{
if (roles.ValueKind == JsonValueKind.Array)
{
foreach (var role in roles.EnumerateArray())
{
identity.AddClaim(new Claim(options.RoleClaim,
role.GetString()));
}
}
else
{
identity.AddClaim(new Claim(options.RoleClaim,
roles.GetString()));
}
}
}
}

return user;
}
}
Client No aplicativo, registre a fábrica em Program.cs :

C#

builder.Services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<CustomUserFactory>();

Server No aplicativo, chame AddRoles no Identity construtor, que adiciona serviços


relacionados à função:

C#

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();

Configurar Identity Servidor


Use uma das seguintes abordagens:

Opções de autorização de API


Serviço de Perfil

Opções de autorização de API

Server No aplicativo:

Configure Identity o Servidor para colocar as name declarações e role no token de


ID e no token de acesso.
Impedir o mapeamento padrão para funções no manipulador de token JWT.

C#

using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Serviço de Perfil
Server No aplicativo, crie uma ProfileService implementação.

ProfileService.cs :

C#

using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using System.Threading.Tasks;

public class ProfileService : IProfileService


{
public ProfileService()
{
}

public async Task GetProfileDataAsync(ProfileDataRequestContext context)


{
var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
context.IssuedClaims.AddRange(nameClaim);

var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);


context.IssuedClaims.AddRange(roleClaims);

await Task.CompletedTask;
}

public async Task IsActiveAsync(IsActiveContext context)


{
await Task.CompletedTask;
}
}

Server No aplicativo, registre o Serviço de Perfil em Program.cs :

C#

using Duende.IdentityServer.Services;

...
builder.Services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Usar mecanismos de autorização


No aplicativo, as Client abordagens de autorização de componentes estão funcionais
neste momento. Qualquer um dos mecanismos de autorização em componentes pode
usar uma função para autorizar o usuário:

AuthorizeView componente (exemplo: <AuthorizeView Roles="Admin"> )

[Authorize] diretiva attribute (AuthorizeAttribute) (exemplo: @attribute


[Authorize(Roles = "Admin")] )

Lógica de procedimento (exemplo: if (user.IsInRole("Admin")) { ... } )

Há suporte para vários testes de função:

C#

if (user.IsInRole("Admin") && user.IsInRole("Developer"))


{
...
}

User.Identity.Name é preenchido no Client aplicativo com o nome de usuário do


usuário, que geralmente é seu endereço de email de entrada.

UserManager e SignInManager
Defina o tipo de declaração do identificador de usuário quando um aplicativo server
exigir:

UserManager<TUser> ou SignInManager<TUser> em um ponto de extremidade


de API.
IdentityUser detalhes, como o nome do usuário, o endereço de email ou a hora de
término do bloqueio.

No Program.cs para ASP.NET Core 6.0 ou posterior:

C#
using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

Em Startup.ConfigureServices para versões de ASP.NET Core anteriores à 6.0:

C#

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

O seguinte WeatherForecastController registra o UserName quando o Get método é


chamado:

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly UserManager<ApplicationUser> userManager;

private static readonly string[] Summaries = new[]


{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm",
"Balmy", "Hot", "Sweltering", "Scorching"
};

private readonly ILogger<WeatherForecastController> logger;


public WeatherForecastController(ILogger<WeatherForecastController>
logger,
UserManager<ApplicationUser> userManager)
{
this.logger = logger;
this.userManager = userManager;
}

[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
var rng = new Random();

var user = await userManager.GetUserAsync(User);

if (user != null)
{
logger.LogInformation($"User.Identity.Name:
{user.UserName}");
}

return Enumerable.Range(1, 5).Select(index => new


WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}

No exemplo anterior:

O Server namespace do projeto é BlazorSample.Server .


O Shared namespace do projeto é BlazorSample.Shared .

Hospedar em Serviço de Aplicativo do Azure


com um domínio e um certificado
personalizados
As diretrizes a seguir explicam:

Como implantar um aplicativo hospedado Blazor WebAssembly com Identity o


Servidor para Serviço de Aplicativo do Azure com um domínio personalizado.
Como criar e usar um certificado TLS para comunicação de protocolo HTTPS com
navegadores. Embora as diretrizes se concentrem em usar o certificado com um
domínio personalizado, as diretrizes são igualmente aplicáveis ao uso de um
domínio padrão dos Aplicativos do Azure, por exemplo
contoso.azurewebsites.net .

Para esse cenário de hospedagem, não use o mesmo certificado para Identity a chave
de assinatura de token do Servidor e a comunicação segura HTTPS do site com
navegadores:

Usar certificados diferentes para esses dois requisitos é uma boa prática de
segurança porque isola chaves privadas para cada finalidade.
Os certificados TLS para comunicação com navegadores são gerenciados
independentemente sem afetar a Identity assinatura de token do Servidor.
Quando o Azure Key Vault fornece um certificado para um aplicativo Serviço de
Aplicativo para associação de domínio personalizada, Identity o Servidor não pode
obter o mesmo certificado do Azure Key Vault para assinatura de token. Embora a
configuração Identity do Servidor para usar o mesmo certificado TLS de um
caminho físico seja possível, colocar certificados de segurança no controle do
código-fonte é uma prática ruim e deve ser evitada na maioria dos cenários.

Nas diretrizes a seguir, um certificado autoassinado é criado no Azure Key Vault


somente para Identity assinatura de token de servidor. A Identity configuração do
servidor usa o certificado do cofre de chaves por meio do repositório de certificados do
CurrentUser > My aplicativo. Outros certificados usados para tráfego HTTPS com

domínios personalizados são criados e configurados separadamente do certificado de


assinatura do Identity servidor.

Para configurar um aplicativo, Serviço de Aplicativo do Azure e Key Vault do Azure para
hospedar com um domínio personalizado e HTTPS:

1. Crie um plano de Serviço de Aplicativo com um nível de plano igual Basic B1 ou


superior. Serviço de Aplicativo requer uma Basic B1 camada de serviço ou
superior para usar domínios personalizados.

2. Crie um certificado PFX para a comunicação do navegador seguro do site


(protocolo HTTPS) com um nome comum do FQDN (nome de domínio totalmente
qualificado) do site que sua organização controla (por exemplo, www.contoso.com ).
Crie o certificado com:

Usos de chave
Validação de assinatura digital ( digitalSignature )
Codificação de chave ( keyEncipherment )
Usos avançados/estendidos de chave
Autenticação do cliente (1.3.6.1.5.5.7.3.2)
Autenticação de Servidor (1.3.6.1.5.5.7.3.1)

Para criar o certificado, use uma das seguintes abordagens ou qualquer outra
ferramenta ou serviço online adequado:

Cofre da Chave do Azure


MakeCert no Windows
OpenSSL

Anote a senha, que é usada posteriormente para importar o certificado para o


Azure Key Vault.

Para obter mais informações sobre certificados de Key Vault do Azure, consulte
Azure Key Vault: Certificados.

3. Crie uma nova Key Vault do Azure ou use um cofre de chaves existente em sua
assinatura do Azure.

4. Na área Certificados do cofre de chaves, importe o certificado do site PFX. Registre


a impressão digital do certificado, que é usada na configuração do aplicativo
posteriormente.

5. No Azure Key Vault, gere um novo certificado autoassinado para Identity


assinatura de token de servidor. Dê ao certificado um nome de certificado e uma
entidade. A Entidade é especificada como CN={COMMON NAME} , em que o {COMMON
NAME} espaço reservado é o nome comum do certificado. O nome comum pode
ser qualquer cadeia de caracteres alfanumérica. Por exemplo,
CN=IdentityServerSigning é uma entidade de certificado válida. EmConfiguração

de Política Avançada de Política> de Emissão, use as configurações padrão.


Registre a impressão digital do certificado, que é usada na configuração do
aplicativo posteriormente.

6. Navegue até Serviço de Aplicativo do Azure no portal do Azure e crie uma nova
Serviço de Aplicativo com a seguinte configuração:

Publicar definido como Code .


Pilha de runtime definida como o runtime do aplicativo.
Para Sku e tamanho, confirme se a camada de Serviço de Aplicativo é Basic
B1 ou superior. Serviço de Aplicativo requer uma Basic B1 camada de

serviço ou superior para usar domínios personalizados.


7. Depois que o Azure criar o Serviço de Aplicativo, abra a Configuração do
aplicativo e adicione uma nova configuração de aplicativo especificando as
impressões digitais do certificado registradas anteriormente. A chave de
configuração do aplicativo é WEBSITE_LOAD_CERTIFICATES . Separe as impressões
digitais do certificado no valor de configuração do aplicativo com uma vírgula,
como mostra o exemplo a seguir:

Chave: WEBSITE_LOAD_CERTIFICATES
Valor:
57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1

No portal do Azure, salvar as configurações do aplicativo é um processo de duas


etapas: Salve a WEBSITE_LOAD_CERTIFICATES configuração chave-valor e, em
seguida, selecione o botão Salvar na parte superior da folha.

8. Selecione as configurações de TLS/SSL do aplicativo. Selecione Certificados de


Chave Privada (.pfx). Use o processo Importar certificado Key Vault. Use o
processo duas vezes para importar o certificado do site para comunicação
HTTPS e o certificado de assinatura de token de servidor autoassinado Identity
do site.

9. Navegue até a folha Domínios personalizados . No site do registrador de domínio,


use o endereço IP e Custom Domain ID de Verificação para configurar o domínio.
Uma configuração de domínio típica inclui:

Um Registro A com um Host de @ e um valor do endereço IP do portal do


Azure.
Um Registro TXT com um Host de asuid e o valor da ID de verificação
gerada pelo Azure e fornecida pelo portal do Azure.

Certifique-se de salvar as alterações no site do registrador de domínio


corretamente. Alguns sites do registrador exigem um processo de duas etapas
para salvar registros de domínio: um ou mais registros são salvos individualmente
seguidos pela atualização do registro do domínio com um botão separado.

10. Retorne à folha Domínios personalizados no portal do Azure. Selecione Adicionar


domínio personalizado. Selecione a opção A Record . Forneça o domínio e
selecione Validar. Se os registros de domínio estiverem corretos e propagados
pela Internet, o portal permitirá que você selecione o botão Adicionar domínio
personalizado .

Pode levar alguns dias para que as alterações de registro de domínio sejam
propagadas entre DNS (servidores de nomes de domínio) da Internet depois que
elas forem processadas pelo registrador de domínio. Se os registros de domínio
não forem atualizados dentro de três dias úteis, confirme se os registros estão
definidos corretamente com o registrador de domínio e entre em contato com o
suporte ao cliente.

11. Na folha Domínios personalizados , o SSL STATE para o domínio está marcado
como Not Secure . Selecione o link Adicionar associação . Selecione o certificado
HTTPS do site no cofre de chaves para a associação de domínio personalizada.

12. No Visual Studio, abra o arquivo de configurações de aplicativo do projeto do


servidor ( appsettings.json ou appsettings.Production.json ). Na configuração do
Identity servidor, adicione a seção a seguir Key . Especifique a Entidade do
certificado autoassinado para a Name chave. No exemplo a seguir, o nome comum
do certificado atribuído no cofre de chaves é IdentityServerSigning , o que gera
uma Entidade de CN=IdentityServerSigning :

JSON

"IdentityServer": {

...

"Key": {
"Type": "Store",
"StoreName": "My",
"StoreLocation": "CurrentUser",
"Name": "CN=IdentityServerSigning"
}
},

13. No Visual Studio, crie um perfil de publicação Serviço de Aplicativo do Azure para
o projeto servidor. Na barra de menus, selecione:
Criar>Publicar>Novo>Azure>Serviço de Aplicativo do Azure (Windows ou
Linux). Quando o Visual Studio estiver conectado a uma assinatura do Azure, você
poderá definir a Exibição de recursos do Azure por tipo de recurso. Navegue na
lista aplicativo Web para encontrar o Serviço de Aplicativo para o aplicativo e
selecioná-lo. Selecione Concluir.

14. Quando o Visual Studio retorna à janela Publicar, o cofre de chaves e SQL Server
dependências do serviço de banco de dados são detectados automaticamente.

Nenhuma alteração de configuração nas configurações padrão é necessária para o


serviço do cofre de chaves.
Para fins de teste, o banco de dados SQLite local de um aplicativo, que é
configurado por padrão pelo Blazor modelo, pode ser implantado com o aplicativo
sem configuração adicional. A configuração de um banco de dados diferente para
Identity o Servidor em produção está além do escopo deste artigo. Para obter mais
informações, consulte os recursos de banco de dados nos seguintes conjuntos de
documentação:

Serviço de Aplicativo
Servidor duende Identity

15. Selecione o link Editar no nome do perfil de implantação na parte superior da


janela. Altere a URL de destino para a URL de domínio personalizada do site (por
exemplo, https://www.contoso.com ). Salve as configurações.

16. Publique o aplicativo. O Visual Studio abre uma janela do navegador e solicita o
site em seu domínio personalizado.

A documentação do Azure contém detalhes adicionais sobre como usar serviços do


Azure e domínios personalizados com associação TLS em Serviço de Aplicativo,
incluindo informações sobre como usar registros CNAME em vez de registros A. Para
saber mais, consulte os recursos a seguir:

Documentação do Serviço de Aplicativo


Tutorial: Mapear um nome DNS personalizado existente para o Serviço de
Aplicativo do Azure
Proteger um nome DNS personalizado com uma associação TLS/SSL no Serviço de
Aplicativo do Azure
Cofre da Chave do Azure

É recomendável usar uma nova janela do navegador privado ou anônimo para cada
execução de teste de aplicativo após uma alteração no aplicativo, na configuração do
aplicativo ou nos serviços do Azure no portal do Azure. cookieOs persistentes de uma
execução de teste anterior podem resultar em falha na autenticação ou autorização ao
testar o site mesmo quando a configuração do site está correta. Para obter mais
informações sobre como configurar o Visual Studio para abrir uma nova janela do
navegador privado ou anônimo para cada execução de teste, consulte a Cookieseção s e
dados do site .

Quando Serviço de Aplicativo configuração é alterada no portal do Azure, as


atualizações geralmente têm efeito rapidamente, mas não são instantâneas. Às vezes,
você deve aguardar um curto período para que um Serviço de Aplicativo seja reiniciado
para que uma alteração de configuração entre em vigor.
Se estiver solucionando um Identity problema de carregamento de certificado de
assinatura de chave do servidor, execute o comando a seguir em um shell de comando
do PowerShell do portal do Azure Kudu . O comando fornece uma lista de certificados
que o aplicativo pode acessar no repositório CurrentUser > My de certificados. A saída
inclui entidades de certificado e impressões digitais úteis ao depurar um aplicativo:

PowerShell

Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList,


Subject, Thumbprint, EnhancedKeyUsageList

Solucionar problemas

Log
Para habilitar o log de depuração ou rastreamento para Blazor WebAssembly
autenticação no ASP.NET Core 7.0 ou posterior, consulte ASP.NET Core Blazor registro
em log.

Erros comuns
Configuração incorreta do aplicativo ou Identity provedor (IP)

Os erros mais comuns são causados pela configuração incorreta. A seguir, estão
alguns exemplos:
Dependendo dos requisitos do cenário, uma Autoridade, Instância, ID do
Locatário, Domínio do Locatário, ID do Cliente ou URI de Redirecionamento
ausente ou incorreto impede que um aplicativo autentique clientes.
Um escopo de token de acesso incorreto impede que os clientes acessem
pontos de extremidade da API Web do servidor.
Permissões incorretas ou ausentes da API do servidor impedem que os clientes
acessem pontos de extremidade da API Web do servidor.
A execução do aplicativo em uma porta diferente da está configurada no URI de
Redirecionamento do Identity registro de aplicativo do Provedor.

As seções de configuração das diretrizes deste artigo mostram exemplos da


configuração correta. Verifique cuidadosamente cada seção do artigo em busca de
configuração incorreta de aplicativo e IP.

Se a configuração parecer correta:


Analisar logs do aplicativo.

Examine o tráfego de rede entre o aplicativo cliente e o aplicativo IP ou servidor


com as ferramentas de desenvolvedor do navegador. Geralmente, uma
mensagem de erro exata ou uma mensagem com uma pista do que está
causando o problema é retornada ao cliente pelo aplicativo IP ou servidor
depois de fazer uma solicitação. Ferramentas de desenvolvedor diretrizes são
encontradas nos seguintes artigos:
Google Chrome (documentação do Google)
Microsoft Edge
Mozilla Firefox (documentação do Mozilla)

Decodificar o conteúdo de um JSToken Web ON (JWT) usado para autenticar


um cliente ou acessar uma API Web do servidor, dependendo de onde o
problema está ocorrendo. Para obter mais informações, consulte Inspecionar o
conteúdo de um JSToken Web ON (JWT).

A equipe de documentação responde a comentários de documentos e bugs em


artigos (abra um problema na seção Comentários desta página ), mas não
consegue fornecer suporte ao produto. Vários fóruns de suporte público estão
disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos
o uso do seguinte:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Os fóruns anteriores não são de propriedade ou controlados por Microsoft.

Para relatórios de bugs de estrutura não confidenciais, não confidenciais e não


confidenciais, abra um problema com a unidade de produto ASP.NET Core . Não
abra um problema com a unidade de produto até que você tenha investigado
minuciosamente a causa de um problema e não possa resolvê-lo por conta própria
e com a ajuda da comunidade em um fórum de suporte público. A unidade de
produto não é capaz de solucionar problemas de aplicativos individuais que foram
interrompidos devido a simples erros de configuração ou casos de uso envolvendo
serviços de terceiros. Se um relatório for confidencial ou confidencial por natureza
ou descrever uma possível falha de segurança no produto que os invasores podem
explorar, consulte Relatando problemas de segurança e bugs (repositório GitHub
dotnet/aspnetcore) .

Cliente não autorizado para o AAD


info: Microsoft. Falha na autorização de
AspNetCore.Authorization.DefaultAuthorizationService[2]. Esses requisitos não
foram atendidos: DenyAnonymousAuthorizationRequirement: requer um
usuário autenticado.

Erro de retorno de chamada de logon do AAD:


Erro: unauthorized_client
Descrição: AADB2C90058: The provided application is not configured to allow
public clients.

Para resolver o erro:

1. No portal do Azure, acesse o manifesto do aplicativo.


2. Defina o allowPublicClient atributo como null ou true .

Cookies e dados do site


Cookieos dados do site e do s podem persistir entre atualizações de aplicativo e
interferir no teste e na solução de problemas. Desmarque o seguinte ao fazer alterações
no código do aplicativo, alterações na conta de usuário com o provedor ou alterações
na configuração do aplicativo do provedor:

Entradas do cookieusuário
Aplicativos cookies
Dados do site armazenados e armazenados em cache

Uma abordagem para impedir que os dados persistentes cookiede s e site interfiram em
testes e solução de problemas é:

Configurar um navegador
Use um navegador para testar que você pode configurar para excluir todos os
cookie dados do site e sempre que o navegador for fechado.
Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer
alteração no aplicativo, usuário de teste ou configuração do provedor.
Use um comando personalizado para abrir um navegador no modo anônimo ou
privado no Visual Studio:
Abra a caixa de diálogo Procurar com no botão Executar do Visual Studio.
Selecione o botão Adicionar.
Forneça o caminho para o navegador no campo Programa . Os caminhos
executáveis a seguir são locais de instalação típicos para Windows 10. Se o
navegador estiver instalado em um local diferente ou você não estiver usando
Windows 10, forneça o caminho para o executável do navegador.
Microsoft Edge: C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe
Google Chrome: C:\Program Files
(x86)\Google\Chrome\Application\chrome.exe
Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
No campo Argumentos , forneça a opção de linha de comando que o
navegador usa para abrir no modo anônimo ou privado. Alguns navegadores
exigem a URL do aplicativo.
Microsoft Edge: use -inprivate .
Google Chrome: use --incognito --new-window {URL} , em que o espaço
reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Mozilla Firefox: use -private -url {URL} , em que o espaço reservado {URL}
é a URL a ser aberta (por exemplo, https://localhost:5001 ).
Forneça um nome no campo Nome amigável . Por exemplo, Firefox Auth
Testing .

Selecione o botão OK.


Para evitar a necessidade de selecionar o perfil do navegador para cada iteração
de teste com um aplicativo, defina o perfil como o padrão com o botão Definir
como Padrão .
Verifique se o navegador está fechado pelo IDE para qualquer alteração no
aplicativo, usuário de teste ou configuração do provedor.

Atualizações de aplicativos
Um aplicativo funcional pode falhar imediatamente após atualizar o SDK do .NET Core
no computador de desenvolvimento ou alterar as versões do pacote dentro do
aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao
executar atualizações principais. A maioria desses problemas pode ser corrigida
seguindo estas instruções:

1. Limpe os caches de pacote NuGet do sistema local executando dotnet nuget locals
all --clear de um shell de comando.
2. Exclua as pastas e obj do bin projeto.
3. Restaure e recompile o projeto.
4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar
o aplicativo.

7 Observação
Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de
destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do
NuGet ou o Gerenciador de Pacotes FuGet .

Executar o aplicativo Servidor


Ao testar e solucionar problemas de uma solução hospedadaBlazor WebAssembly,
verifique se você está executando o aplicativo do Server projeto. Por exemplo, no Visual
Studio, confirme se o projeto Do servidor está realçado em Gerenciador de Soluções
antes de iniciar o aplicativo com qualquer uma das seguintes abordagens:

Selecione o botão Executar.


Use Depurar>Iniciar Depuração no menu.
Pressione F5 .

Inspecionar o usuário
Os ativos de teste da estrutura ASP.NET Core incluem um Blazor WebAssembly
aplicativo cliente com um User componente que pode ser útil na solução de
problemas. O User componente pode ser usado diretamente em aplicativos ou servir
como base para personalização adicional:

User componente de teste no dotnet/aspnetcore repositório GitHub

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Inspecionar o conteúdo de um JSToken Web ON (JWT)


Para decodificar um JSToken Web ON (JWT), use a ferramenta de jwt.ms do Microsoft .
Os valores na interface do usuário nunca saem do navegador.

Exemplo de JWT codificado (abreviado para exibição):


eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j ...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q

Exemplo de JWT decodificado pela ferramenta para um aplicativo que se autentica no


Azure AAD B2C:

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Recursos adicionais
Implantação no Serviço de Aplicativo do Azure
Importar um certificado do Key Vault (documentação do Azure)
Cenários de segurança adicionais do ASP.NET Core Blazor WebAssembly
Solicitações de API Web não autenticadas ou não autorizadas em um aplicativo
com um cliente padrão seguro
Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores
de carga: inclui diretrizes sobre:
O uso de Middleware de Cabeçalhos Encaminhados para preservar informações
do esquema HTTPS entre servidores proxy e redes internas.
Cenários e casos de uso adicionais, incluindo configuração de esquema manual,
solicitação de alterações de caminho para roteamento de solicitação correto e
encaminhamento do esquema de solicitação para proxies reversos do Linux e
não IIS.
Servidor duende Identity
Cenários de segurança adicionais do
ASP.NET Core Blazor WebAssembly
Artigo • 21/12/2022 • 117 minutos para o fim da leitura

Este artigo descreve cenários de segurança adicionais para Blazor WebAssembly


aplicativos.

Anexar tokens a solicitações de saída


AuthorizationMessageHandler é um DelegatingHandler usado para processar tokens de
acesso. Os tokens são adquiridos usando o IAccessTokenProvider serviço , que é
registrado pela estrutura . Se um token não puder ser adquirido, um
AccessTokenNotAvailableException será gerado. AccessTokenNotAvailableException tem
um Redirect método que navega para AccessTokenResult.InteractiveRequestUrl
usando o fornecido AccessTokenResult.InteractionOptions para permitir a atualização
do token de acesso.

Para sua conveniência, a estrutura fornece o BaseAddressAuthorizationMessageHandler


pré-configurado com o endereço base do aplicativo como uma URL autorizada. Os
tokens de acesso só são adicionados quando o URI de solicitação está dentro do URI
base do aplicativo. Quando os URIs de solicitação de saída não estiverem dentro do URI
base do aplicativo, use uma classe personalizada AuthorizationMessageHandler
(recomendado) ou configure o AuthorizationMessageHandler.

7 Observação

Além da configuração do aplicativo cliente para acesso à API do servidor, a API do


servidor também deve permitir solicitações entre origens (CORS) quando o cliente
e o servidor não residem no mesmo endereço base. Para obter mais informações
sobre a configuração do CORS do lado do servidor, consulte a seção CORS
(compartilhamento de recursos entre origens) mais adiante neste artigo.

No exemplo a seguir:

AddHttpClient adiciona IHttpClientFactory e serviços relacionados à coleção de


serviços e configura um nomeado HttpClient ( WebAPI ). HttpClient.BaseAddress é o
endereço base do URI do recurso ao enviar solicitações. IHttpClientFactory é
fornecido pelo Microsoft.Extensions.Http pacote NuGet.
BaseAddressAuthorizationMessageHandler é o DelegatingHandler usado para
processar tokens de acesso. Os tokens de acesso só são adicionados quando o URI
de solicitação está dentro do URI base do aplicativo.
IHttpClientFactory.CreateClient cria e configura uma HttpClient instância para
solicitações de saída usando a configuração que corresponde ao nomeado
HttpClient ( WebAPI ).

C#

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

// AddHttpClient is an extension in Microsoft.Extensions.Http


builder.Services.AddHttpClient("WebAPI",
client => client.BaseAddress = new
Uri("https://www.example.com/base"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()


.CreateClient("WebAPI"));

Para uma solução hospedada Blazor com base no Blazor WebAssembly modelo de
projeto, os URIs de solicitação estão dentro do URI base do aplicativo por padrão.
Portanto, IWebAssemblyHostEnvironment.BaseAddress ( new
Uri(builder.HostEnvironment.BaseAddress) ) é atribuído ao HttpClient.BaseAddress em

um aplicativo gerado a partir do modelo de projeto.

O configurado HttpClient é usado para fazer solicitações autorizadas usando o try-catch


padrão :

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()


{
private ExampleType[] examples;

try
{
examples =
await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

...
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}

Cenários de solicitação de autenticação


personalizada
Os cenários a seguir demonstram como personalizar solicitações de autenticação e
como obter o caminho de logon das opções de autenticação.

Personalizar o processo de logon


Gerencie parâmetros adicionais para uma solicitação de logon com os seguintes
métodos uma ou mais vezes em uma nova instância do InteractiveRequestOptions:

TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter

No exemplo de componente a seguir LoginDisplay , parâmetros adicionais são


adicionados à solicitação de logon:

prompt é definido login como : força o usuário a inserir suas credenciais nessa

solicitação, negando o logon único.


loginHint é definido peter@contoso.com como : preenche previamente o campo

nome de usuário/endereço de email da página de entrada do usuário para


peter@contoso.com . Os aplicativos geralmente usam esse parâmetro durante a

nova autenticação, já tendo extraído o nome de usuário de uma entrada anterior


usando a preferred_username declaração .

Shared/LoginDisplay.razor :

C#

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation

<AuthorizeView>
<Authorized>
Hello, @context.User.Identity?.Name!
<button class="nav-link btn btn-link" @onclick="BeginLogOut">Log
out</button>
</Authorized>
<NotAuthorized>
<button class="nav-link btn btn-link" @onclick="BeginLogIn">Log
in</button>
</NotAuthorized>
</AuthorizeView>

@code{
public void BeginLogOut()
{
Navigation.NavigateToLogout("authentication/logout");
}

public void BeginLogIn()


{
InteractiveRequestOptions requestOptions =
new()
{
Interaction = InteractionType.SignIn,
ReturnUrl = Navigation.Uri,
};

requestOptions.TryAddAdditionalParameter("prompt", "login");
requestOptions.TryAddAdditionalParameter("loginHint",
"peter@contoso.com");

Navigation.NavigateToLogin("authentication/login", requestOptions);
}
}

Para saber mais, consulte os recursos a seguir:

InteractiveRequestOptions
Lista de parâmetros de solicitação pop-up

Personalizar opções antes de obter um token de modo


interativo
Se ocorrer um AccessTokenNotAvailableException , gerencie parâmetros adicionais para
uma nova solicitação de token de acesso do provedor de identidade com os seguintes
métodos uma ou mais vezes em uma nova instância do InteractiveRequestOptions:

TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter
No exemplo a seguir que obtém JSdados ON por meio da API Web, parâmetros
adicionais são adicionados à solicitação de redirecionamento se um token de acesso
não estiver disponível (AccessTokenNotAvailableException é gerado):

prompt é definido login como : força o usuário a inserir suas credenciais nessa
solicitação, negando o logon único.
loginHint é definido peter@contoso.com como : preenche previamente o campo

nome de usuário/endereço de email da página de entrada do usuário para


peter@contoso.com . Os aplicativos geralmente usam esse parâmetro durante a

nova autenticação, já tendo extraído o nome de usuário de uma entrada anterior


usando a preferred_username declaração .

C#

try
{
examples = await Http.GetFromJsonAsync<ExampleType[]>
("ExampleAPIMethod");

...
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect(requestOptions => {
requestOptions.TryAddAdditionalParameter("prompt", "login");
requestOptions.TryAddAdditionalParameter("loginHint",
"peter@contoso.com");
});
}

O exemplo anterior pressupõe que:

A presença de uma instrução @using / using para a API no


Microsoft.AspNetCore.Components.WebAssembly.Authentication namespace .
HttpClient injetado como Http .

Para saber mais, consulte os recursos a seguir:

InteractiveRequestOptions
Lista de parâmetros de solicitação de redirecionamento

Personalizar opções ao usar um IAccessTokenProvider


Se a obtenção de um token falhar ao usar um IAccessTokenProvider, gerencie
parâmetros adicionais para uma nova solicitação de token de acesso do provedor de
identidade com os seguintes métodos uma ou mais vezes em uma nova instância do
InteractiveRequestOptions:

TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter

No exemplo a seguir que tenta obter um token de acesso para o usuário, parâmetros
adicionais são adicionados à solicitação de logon se a tentativa de obter um token
falhar quando TryGetToken for chamada:

prompt é definido login como : força o usuário a inserir suas credenciais nessa
solicitação, negando o logon único.
loginHint é definido peter@contoso.com como : preenche previamente o campo

nome de usuário/endereço de email da página de entrada do usuário para


peter@contoso.com . Os aplicativos geralmente usam esse parâmetro durante a

nova autenticação, já tendo extraído o nome de usuário de uma entrada anterior


usando a preferred_username declaração .

C#

var tokenResult = await TokenProvider.RequestAccessToken(


new AccessTokenRequestOptions
{
Scopes = new[] { ... }
});

if (!tokenResult.TryGetToken(out var token))


{
tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt",
"login");
tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint",
"peter@contoso.com");

Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl,
accessTokenResult.InteractionOptions);
}

O exemplo anterior pressupõe:

A presença de uma instrução @using / using para a API no


Microsoft.AspNetCore.Components.WebAssembly.Authentication namespace .
IAccessTokenProvider injetado como TokenProvider .

Para saber mais, consulte os recursos a seguir:

InteractiveRequestOptions
Lista de parâmetros de solicitação pop-up

Logoff com uma URL de retorno personalizada


O exemplo a seguir faz logoff do usuário e retorna o usuário para o /goodbye ponto de
extremidade:

C#

Navigation.NavigateToLogout("authentication/logout", "goodbye");

Obter o caminho de logon das opções de autenticação


Obtenha o caminho de logon configurado de RemoteAuthenticationOptions:

C#

var loginPath =
RemoteAuthOptions.Get(Options.DefaultName).AuthenticationPaths.LogInPath;

O exemplo anterior pressupõe:

A presença de uma instrução @using / using para a API nos seguintes namespaces:
Microsoft.AspNetCore.Components.WebAssembly.Authentication
Microsoft.Extensions.Options
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>

> injetado como RemoteAuthOptions .

Classe personalizada AuthorizationMessageHandler


Estas diretrizes nesta seção são recomendadas para aplicativos cliente que fazem
solicitações de saída para URIs que não estão dentro do URI base do aplicativo.

No exemplo a seguir, uma classe personalizada se estende


AuthorizationMessageHandler para uso como para DelegatingHandler um HttpClient.
ConfigureHandler configura esse manipulador para autorizar solicitações HTTP de saída
usando um token de acesso. O token de acesso só será anexado se pelo menos uma das
URLs autorizadas for uma base do URI de solicitação (HttpRequestMessage.RequestUri).

C#

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigation)
: base(provider, navigation)
{
ConfigureHandler(
authorizedUrls: new[] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" });
}
}

No código anterior, os escopos example.read e example.write são exemplos genéricos


que não se destinam a refletir escopos válidos para qualquer provedor específico. Para
aplicativos que usam o Azure Active Directory, os escopos são semelhantes a
api://41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access (domínio de editor confiável)
ou https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access
(domínio de editor não confiável).

No Program.cs , CustomAuthorizationMessageHandler é registrado como um serviço


transitório e é configurado como o DelegatingHandler para instâncias de saída
HttpResponseMessage feitas por um chamado HttpClient:

C#

builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

// AddHttpClient is an extension in Microsoft.Extensions.Http


builder.Services.AddHttpClient("WebAPI",
client => client.BaseAddress = new
Uri("https://www.example.com/base"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

7 Observação

No exemplo anterior, o CustomAuthorizationMessageHandler DelegatingHandler é


registrado como um serviço transitório para AddHttpMessageHandler. O registro
transitório é recomendado para IHttpClientFactory, que gerencia seus próprios
escopos de DI. Para saber mais, consulte os recursos a seguir:

Classes de componente base do utilitário para gerenciar um escopo de DI


Detectar descartáveis transitórios em Blazor WebAssembly aplicativos
Para uma solução hospedada Blazor com base no modelo de Blazor WebAssembly
projeto, IWebAssemblyHostEnvironment.BaseAddress ( new
Uri(builder.HostEnvironment.BaseAddress) ) é atribuído ao HttpClient.BaseAddress por

padrão.

O configurado HttpClient é usado para fazer solicitações autorizadas usando o try-catch


padrão . Onde o cliente é criado com CreateClient (Microsoft.Extensions.Http pacote),
o HttpClient é fornecido instâncias que incluem tokens de acesso ao fazer solicitações
para a API do servidor. Se o URI de solicitação for um URI relativo, como está no
exemplo a seguir ( ExampleAPIMethod ), ele será combinado com o BaseAddress quando o
aplicativo cliente fizer a solicitação:

razor

@inject IHttpClientFactory ClientFactory

...

@code {
private ExampleType[] examples;

protected override async Task OnInitializedAsync()


{
try
{
var client = ClientFactory.CreateClient("WebAPI");

examples =
await client.GetFromJsonAsync<ExampleType[]>
("ExampleAPIMethod");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}

configurar AuthorizationMessageHandler
AuthorizationMessageHandler pode ser configurado com URLs autorizadas, escopos e
uma URL de retorno usando o ConfigureHandler método . ConfigureHandler configura
o manipulador para autorizar solicitações HTTP de saída usando um token de acesso. O
token de acesso só será anexado se pelo menos uma das URLs autorizadas for uma base
do URI de solicitação (HttpRequestMessage.RequestUri). Se o URI da solicitação for um
URI relativo, ele será combinado com o BaseAddress.
No exemplo a seguir, AuthorizationMessageHandler configura um HttpClient em
Program.cs :

C#

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(


sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new[] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" }))
{
BaseAddress = new Uri("https://www.example.com/base")
});

No código anterior, os escopos example.read e example.write são exemplos genéricos


que não se destinam a refletir escopos válidos para qualquer provedor específico. Para
aplicativos que usam o Azure Active Directory, os escopos são semelhantes a
api://41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access (domínio de editor confiável)
ou https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access
(domínio de editor não confiável).

Para uma solução hospedada Blazor com base no Blazor WebAssembly modelo de
projeto, IWebAssemblyHostEnvironment.BaseAddress é atribuída ao seguinte por
padrão:

O HttpClient.BaseAddress ( new Uri(builder.HostEnvironment.BaseAddress) ).


Uma URL da authorizedUrls matriz.

Digitado HttpClient
Um cliente tipado pode ser definido que lida com todas as preocupações de aquisição
de HTTP e token em uma única classe.

WeatherForecastClient.cs :

C#

using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {APP ASSEMBLY}.Data;

public class WeatherForecastClient


{
private readonly HttpClient http;

public WeatherForecastClient(HttpClient http)


{
this.http = http;
}

public async Task<WeatherForecast[]> GetForecastAsync()


{
var forecasts = new WeatherForecast[0];

try
{
forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
"WeatherForecast");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}

return forecasts;
}
}

O espaço reservado {APP ASSEMBLY} é o nome do assembly do aplicativo (por exemplo,


using static BlazorSample.Data; ).

Em Program.cs :

C#

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

// AddHttpClient is an extension in Microsoft.Extensions.Http


builder.Services.AddHttpClient<WeatherForecastClient>(
client => client.BaseAddress = new
Uri("https://www.example.com/base"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Para uma solução hospedada Blazor com base no modelo de Blazor WebAssembly
projeto, IWebAssemblyHostEnvironment.BaseAddress ( new
Uri(builder.HostEnvironment.BaseAddress) ) é atribuído ao HttpClient.BaseAddress por

padrão.

FetchData componente ( Pages/FetchData.razor ):

razor

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()


{
forecasts = await Client.GetForecastAsync();
}

Configurar o HttpClient manipulador


O manipulador pode ser configurado com ConfigureHandler para solicitações HTTP de
saída.

Em Program.cs :

C#

// AddHttpClient is an extension in Microsoft.Extensions.Http


builder.Services.AddHttpClient<WeatherForecastClient>(
client => client.BaseAddress = new
Uri("https://www.example.com/base"))
.AddHttpMessageHandler(sp =>
sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new [] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" }));

No código anterior, os escopos example.read e example.write são exemplos genéricos


não destinados a refletir escopos válidos para qualquer provedor específico. Para
aplicativos que usam o Azure Active Directory, os escopos são semelhantes a
api://41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access (domínio de editor confiável)

ou https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access
(domínio de editor não confiável).

Para uma solução hospedada Blazor com base no Blazor WebAssembly modelo de
projeto, IWebAssemblyHostEnvironment.BaseAddress é atribuída ao seguinte por
padrão:
O HttpClient.BaseAddress ( new Uri(builder.HostEnvironment.BaseAddress) ).
Uma URL da authorizedUrls matriz.

Solicitações de API Web não autenticadas ou


não autorizadas em um aplicativo com um
cliente padrão seguro
Se o Blazor WebAssembly aplicativo normalmente usa um padrão HttpClientseguro, o
aplicativo também pode fazer solicitações de API Web não autenticadas ou não
autorizadas configurando um chamado HttpClient:

Em Program.cs :

C#

// AddHttpClient is an extension in Microsoft.Extensions.Http


builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient",
client => client.BaseAddress = new Uri("https://www.example.com/base"));

Para uma solução hospedada Blazor com base no modelo de Blazor WebAssembly
projeto, IWebAssemblyHostEnvironment.BaseAddress ( new
Uri(builder.HostEnvironment.BaseAddress) ) é atribuído ao HttpClient.BaseAddress por

padrão.

O registro anterior é além do registro padrão HttpClient seguro existente.

Um componente cria o HttpClient do IHttpClientFactory (Microsoft.Extensions.Http


pacote) para fazer solicitações não autenticadas ou não autorizadas:

razor

@inject IHttpClientFactory ClientFactory

...

@code {
private WeatherForecast[] forecasts;

protected override async Task OnInitializedAsync()


{
var client =
ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(


"WeatherForecastNoAuthentication");
}
}

7 Observação

O controlador na API do servidor, WeatherForecastNoAuthenticationController para


o exemplo anterior, não está marcado com o [Authorize] atributo .

A decisão de usar um cliente seguro ou um cliente inseguro como a instância padrão


HttpClient cabe ao desenvolvedor. Uma maneira de tomar essa decisão é considerar o
número de pontos de extremidade autenticados versus não autenticados que o
aplicativo contata. Se a maioria das solicitações do aplicativo for proteger pontos de
extremidade de API, use a instância autenticada HttpClient como padrão. Caso
contrário, registre a instância não autenticada HttpClient como o padrão.

Uma abordagem alternativa para usar o IHttpClientFactory é criar um cliente tipado para
acesso não autenticado a pontos de extremidade anônimos.

Solicitar tokens de acesso adicionais


Os tokens de acesso podem ser obtidos manualmente chamando
IAccessTokenProvider.RequestAccessToken. No exemplo a seguir, um escopo adicional é
exigido por um aplicativo para o padrão HttpClient. O exemplo da MSAL (Biblioteca de
Autenticação Microsoft) configura o escopo com MsalProviderOptions :

Em Program.cs :

C#

builder.Services.AddMsalAuthentication(options =>
{
...

options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE
1}");
options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE
2}");
}

Os {CUSTOM SCOPE 1} espaços reservados e {CUSTOM SCOPE 2} no exemplo anterior são


escopos personalizados.
O IAccessTokenProvider.RequestAccessToken método fornece uma sobrecarga que
permite que um aplicativo provisione um token de acesso com um determinado
conjunto de escopos.

Em um Razor componente:

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(


new AccessTokenRequestOptions
{
Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
});

if (tokenResult.TryGetToken(out var token))


{
...
}

Os {CUSTOM SCOPE 1} espaços reservados e {CUSTOM SCOPE 2} no exemplo anterior são


escopos personalizados.

O AccessTokenResult.TryGetToken retorna:

true com o token para uso.

false se o token não for recuperado.

CORS (Compartilhamento de Recursos entre


Origens)
Ao enviar credenciais (cabeçalhos/s de autorização cookie) em solicitações CORS, o
Authorization cabeçalho deve ser permitido pela política CORS.

A política a seguir inclui a configuração para:

Origens da solicitação ( http://localhost:5000 , https://localhost:5001 ).


Qualquer método (verbo).
Content-Type cabeçalhos e Authorization . Para permitir um cabeçalho

personalizado (por exemplo, x-custom-header ), liste o cabeçalho ao chamar


WithHeaders.
Credenciais definidas pelo código JavaScript do lado do cliente ( credentials
propriedade definida como include ).

C#

app.UseCors(policy =>
policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
.AllowAnyMethod()
.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-
custom-header")
.AllowCredentials());

Uma solução hospedada Blazor com base no Blazor WebAssembly modelo de projeto
usa o mesmo endereço base para os aplicativos cliente e servidor. O do aplicativo
HttpClient.BaseAddress cliente é definido como um URI de
builder.HostEnvironment.BaseAddress por padrão. A configuração do CORS não é
necessária na configuração padrão de uma solução hospedada Blazor . Aplicativos
cliente adicionais que não são hospedados pelo projeto do servidor e não compartilham
o endereço base do aplicativo de servidor exigem a configuração de CORS no projeto
do servidor.

Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no
ASP.NET Core e o componente do Testador de Solicitação HTTP do aplicativo de
exemplo ( Components/HTTPRequestTester.razor ).

Manipular erros de solicitação de token


Quando um SPA (aplicativo de página única) autentica um usuário usando o OIDC
(OpenID Connect), o estado de autenticação é mantido localmente dentro do SPA e no
Identity Provedor (IP) na forma de uma sessão cookie definida como resultado do
usuário fornecer suas credenciais.

Os tokens que o IP emite para o usuário normalmente são válidos por curtos períodos
de tempo, cerca de uma hora normalmente, portanto, o aplicativo cliente deve buscar
regularmente novos tokens. Caso contrário, o usuário será conectado após a expiração
dos tokens concedidos. Na maioria dos casos, os clientes OIDC são capazes de
provisionar novos tokens sem exigir que o usuário se autentique novamente graças ao
estado de autenticação ou à "sessão" mantida dentro do IP.

Há alguns casos em que o cliente não pode obter um token sem interação do usuário,
por exemplo, quando, por algum motivo, o usuário faz logoff explicitamente do IP. Esse
cenário ocorre se um usuário visita https://login.microsoftonline.com e faz logoff.
Nesses cenários, o aplicativo não sabe imediatamente que o usuário fez logon.
Qualquer token que o cliente contém pode não ser mais válido. Além disso, o cliente
não pode provisionar um novo token sem a interação do usuário após a expiração do
token atual.

Esses cenários não são específicos para a autenticação baseada em token. Eles fazem
parte da natureza dos SPAs. Um SPA usando cookies também falha ao chamar uma API
de servidor se a autenticação cookie for removida.

Quando um aplicativo executa chamadas à API para recursos protegidos, você deve
estar ciente do seguinte:

Para provisionar um novo token de acesso para chamar a API, o usuário pode ser
obrigado a autenticar novamente.
Mesmo que o cliente tenha um token que parece ser válido, a chamada para o
servidor pode falhar porque o token foi revogado pelo usuário.

Quando o aplicativo solicita um token, há dois resultados possíveis:

A solicitação é bem-sucedida e o aplicativo tem um token válido.


A solicitação falha e o aplicativo deve autenticar o usuário novamente para obter
um novo token.

Quando uma solicitação de token falha, você precisa decidir se deseja salvar qualquer
estado atual antes de executar um redirecionamento. Existem várias abordagens com
níveis crescentes de complexidade:

Armazene o estado da página atual no armazenamento de sessão. Durante o


método de ciclo de vida (), verifique se oOnInitializedAsync estado pode ser
restaurado antes de continuar.OnInitializedAsync
Adicione um parâmetro de cadeia de caracteres de consulta e use-o como uma
maneira de sinalizar ao aplicativo que ele precisa hidratar novamente o estado
salvo anteriormente.
Adicione um parâmetro de cadeia de caracteres de consulta com um identificador
exclusivo para armazenar dados no armazenamento de sessão sem correr o risco
de colisões com outros itens.

O exemplo a seguir mostra como:

Preserve o estado antes de redirecionar para a página de logon.


Recupere o estado anterior depois da autenticação usando o parâmetro de cadeia
de caracteres de consulta.

razor
...
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
...

<EditForm Model="User" @onsubmit="OnSaveAsync">


<label>User
<InputText @bind-Value="User.Name" />
</label>
<label>Last name
<InputText @bind-Value="User.LastName" />
</label>
</EditForm>

@code {
public class Profile
{
public string Name { get; set; }
public string LastName { get; set; }
}

public Profile User { get; set; } = new Profile();

protected override async Task OnInitializedAsync()


{
var currentQuery = new Uri(Navigation.Uri).Query;

if (currentQuery.Contains("state=resumeSavingProfile"))
{
User = await JS.InvokeAsync<Profile>("sessionStorage.getState",
"resumeSavingProfile");
}
}

public async Task OnSaveAsync()


{
var http = new HttpClient();
http.BaseAddress = new Uri(Navigation.BaseUri);

var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

var tokenResult = await TokenProvider.RequestAccessToken(


new AccessTokenRequestOptions
{
ReturnUrl = resumeUri
});

if (tokenResult.TryGetToken(out var token))


{
http.DefaultRequestHeaders.Add("Authorization",
$"Bearer {token.Value}");
await http.PostAsJsonAsync("Save", User);
}
else
{
await JS.InvokeVoidAsync("sessionStorage.setState",
"resumeSavingProfile", User);
Navigation.NavigateTo(tokenResult.InteractiveRequestUrl);
}
}
}

Salvar o estado do aplicativo antes de uma


operação de autenticação
Durante uma operação de autenticação, há casos em que você deseja salvar o estado
do aplicativo antes que o navegador seja redirecionado para o IP. Esse pode ser o caso
quando você está usando um contêiner de estado e deseja restaurar o estado depois
que a autenticação for bem-sucedida. Você pode usar um objeto de estado de
autenticação personalizado para preservar o estado específico do aplicativo ou uma
referência a ele e restaurar esse estado após a conclusão bem-sucedida da operação de
autenticação. O exemplo a seguir demonstra a abordagem.

Uma classe de contêiner de estado é criada no aplicativo com propriedades para manter
os valores de estado do aplicativo. No exemplo a seguir, o contêiner é usado para
manter o valor do contador do componente do modelo de projeto padrãoBlazor
( Pages/Counter.razor ). Counter Os métodos para serializar e desserializar o contêiner
são baseados em System.Text.Json.

C#

using System.Text.Json;

public class StateContainer


{
public int CounterValue { get; set; }

public string GetStateForLocalStorage()


{
return JsonSerializer.Serialize(this);
}

public void SetStateFromLocalStorage(string locallyStoredState)


{
var deserializedState =
JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

CounterValue = deserializedState.CounterValue;
}
}
O Counter componente usa o contêiner de estado para manter o currentCount valor
fora do componente:

razor

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

protected override void OnInitialized()


{
if (State.CounterValue > 0)
{
currentCount = State.CounterValue;
}
}

private void IncrementCount()


{
currentCount++;
State.CounterValue = currentCount;
}
}

Crie um ApplicationAuthenticationState de RemoteAuthenticationState. Forneça uma


Id propriedade , que serve como um identificador para o estado armazenado

localmente.

ApplicationAuthenticationState.cs :

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState


{
public string Id { get; set; }
}

O Authentication componente ( Pages/Authentication.razor ) salva e restaura o estado


do aplicativo usando o armazenamento de sessão local com os StateContainer
métodos GetStateForLocalStorage de serialização e desserialização e
SetStateFromLocalStorage :

razor

@page "/authentication/{action}"
@inject IJSRuntime JS
@inject StateContainer State
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorViewCore Action="@Action"

TAuthenticationState="ApplicationAuthenticationState"
AuthenticationState="AuthenticationState"
OnLogInSucceeded="RestoreState"
OnLogOutSucceeded="RestoreState" />

@code {
[Parameter]
public string Action { get; set; }

public ApplicationAuthenticationState AuthenticationState { get; set; }


=
new ApplicationAuthenticationState();

protected override async Task OnInitializedAsync()


{
if
(RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
Action) ||

RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
Action))
{
AuthenticationState.Id = Guid.NewGuid().ToString();

await JS.InvokeVoidAsync("sessionStorage.setItem",
AuthenticationState.Id, State.GetStateForLocalStorage());
}
}

private async Task RestoreState(ApplicationAuthenticationState state)


{
if (state.Id != null)
{
var locallyStoredState = await JS.InvokeAsync<string>(
"sessionStorage.getItem", state.Id);

if (locallyStoredState != null)
{
State.SetStateFromLocalStorage(locallyStoredState);
await JS.InvokeVoidAsync("sessionStorage.removeItem",
state.Id);
}
}
}
}

No exemplo anterior, JS é uma instância injetada IJSRuntime . IJSRuntime é registrado


pela Blazor estrutura.

Este exemplo usa o AAD (Azure Active Directory) para autenticação. Em Program.cs :

O ApplicationAuthenticationState é configurado como o tipo MSAL


RemoteAuthenticationState (Biblioteca de Autenticação Microsoft).

O contêiner de estado é registrado no contêiner de serviço.

C#

builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>
(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

Personalizar rotas de aplicativo


Por padrão, a Microsoft.AspNetCore.Components.WebAssembly.Authentication
biblioteca usa as rotas mostradas na tabela a seguir para representar diferentes estados
de autenticação.

Rota Finalidade

authentication/login Dispara uma operação de entrada.

authentication/login- Manipula o resultado de qualquer operação de entrada.


callback

authentication/login- Exibe mensagens de erro quando a operação de entrada falha por


failed algum motivo.

authentication/logout Dispara uma operação de saída.

authentication/logout- Manipula o resultado de uma operação de saída.


callback
Rota Finalidade

authentication/logout- Exibe mensagens de erro quando a operação de saída falha por


failed algum motivo.

authentication/logged- Indica que o usuário fez logoff com êxito.


out

authentication/profile Dispara uma operação para editar o perfil do usuário.

authentication/register Dispara uma operação para registrar um novo usuário.

As rotas mostradas na tabela anterior são configuráveis por meio de


RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.Authenticatio
nPaths. Ao definir opções para fornecer rotas personalizadas, confirme se o aplicativo
tem uma rota que manipula cada caminho.

No exemplo a seguir, todos os caminhos são prefixados com /security .

Authentication componente ( Pages/Authentication.razor ):

razor

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
[Parameter]
public string Action { get; set; }
}

Em Program.cs :

C#

builder.Services.AddApiAuthorization(options => {
options.AuthenticationPaths.LogInPath = "security/login";
options.AuthenticationPaths.LogInCallbackPath = "security/login-
callback";
options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
options.AuthenticationPaths.LogOutPath = "security/logout";
options.AuthenticationPaths.LogOutCallbackPath = "security/logout-
callback";
options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
options.AuthenticationPaths.ProfilePath = "security/profile";
options.AuthenticationPaths.RegisterPath = "security/register";
});

Se o requisito chamar caminhos completamente diferentes, defina as rotas conforme


descrito anteriormente e renderize o RemoteAuthenticatorView com um parâmetro de
ação explícito:

razor

@page "/register"

<RemoteAuthenticatorView Action="@RemoteAuthenticationActions.Register" />

Você poderá dividir a interface do usuário em páginas diferentes se optar por fazer isso.

Personalizar a interface do usuário de


autenticação
RemoteAuthenticatorView inclui um conjunto padrão de partes da interface do usuário
para cada estado de autenticação. Cada estado pode ser personalizado passando um
personalizado RenderFragment. Para personalizar o texto exibido durante o processo de
logon inicial, pode alterar o RemoteAuthenticatorView da seguinte maneira.

Authentication componente ( Pages/Authentication.razor ):

razor

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
<LoggingIn>
You are about to be redirected to https://login.microsoftonline.com.
</LoggingIn>
</RemoteAuthenticatorView>

@code{
[Parameter]
public string Action { get; set; }
}

O RemoteAuthenticatorView tem um fragmento que pode ser usado por rota de


autenticação mostrado na tabela a seguir.

Rota Fragmento
Rota Fragmento

authentication/login <LoggingIn>

authentication/login-callback <CompletingLoggingIn>

authentication/login-failed <LogInFailed>

authentication/logout <LogOut>

authentication/logout-callback <CompletingLogOut>

authentication/logout-failed <LogOutFailed>

authentication/logged-out <LogOutSucceeded>

authentication/profile <UserProfile>

authentication/register <Registering>

Personalizar o usuário
Os usuários associados ao aplicativo podem ser personalizados.

Personalizar o usuário com uma declaração de conteúdo


No exemplo a seguir, os usuários autenticados do aplicativo recebem uma declaração
amr para cada um dos métodos de autenticação do usuário. A amr declaração identifica
como o assunto do token foi autenticado em declarações de conteúdo do Microsoft
Identity Platform v1.0. O exemplo usa uma classe de conta de usuário personalizada
com base em RemoteUserAccount.

Crie uma classe que estenda a RemoteUserAccount classe . O exemplo a seguir define a
AuthenticationMethod propriedade como a matriz do usuário de valores de
amr JSpropriedade ON. AuthenticationMethod é preenchido automaticamente pela

estrutura quando o usuário é autenticado.

C#

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount


{
[JsonPropertyName("amr")]
public string[] AuthenticationMethod { get; set; }
}
Crie uma fábrica que se estenda AccountClaimsPrincipalFactory<TAccount> para criar
declarações com base nos métodos de autenticação do usuário armazenados em
CustomUserAccount.AuthenticationMethod :

C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory


: AccountClaimsPrincipalFactory<CustomUserAccount>
{
public CustomAccountFactory(NavigationManager navigation,
IAccessTokenProviderAccessor accessor) : base(accessor)
{
}

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(


CustomUserAccount account, RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);

if (initialUser.Identity != null &&


initialUser.Identity.IsAuthenticated)
{
var userIdentity = (ClaimsIdentity)initialUser.Identity;

foreach (var value in account.AuthenticationMethod)


{
userIdentity.AddClaim(new Claim("amr", value));
}
}

return initialUser;
}
}

Registre o CustomAccountFactory para o provedor de autenticação em uso. Qualquer um


dos seguintes registros é válido:

AddOidcAuthentication:

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
...

builder.Services.AddOidcAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
...
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();

AddMsalAuthentication:

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
...
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();

AddApiAuthorization:

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddApiAuthorization<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
...
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();

Grupos de segurança e funções do AAD com uma classe


de conta de usuário personalizada
Para obter um exemplo adicional que funciona com grupos de segurança do AAD e
Funções de Administrador do AAD e uma classe de conta de usuário personalizada,
consulte ASP.NET Core Blazor WebAssembly com grupos e funções do Azure Active
Directory.

Pré-geração com autenticação


Atualmente, não há suporte para pré-geração de conteúdo que exija autenticação e
autorização. Depois de seguir as diretrizes em um dos tópicos do Blazor WebAssembly
aplicativo de segurança, use as seguintes instruções para criar um aplicativo que:

Caminhos de pré-remetentes para os quais a autorização não é necessária.


Não cria pré-geração de caminhos para os quais a autorização é necessária.

Para o arquivo do Program.cs projeto, fatore os Client registros de serviço comuns em


um método separado (por exemplo, crie um ConfigureCommonServices método no Client
projeto). Os serviços comuns são aqueles que o desenvolvedor registra para uso pelos
projetos de cliente e servidor.

C#

public static void ConfigureCommonServices(IServiceCollection services)


{
services.Add...;
}

Program.cs :

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);


...

builder.Services.AddScoped( ... );

ConfigureCommonServices(builder.Services);

await builder.Build().RunAsync();

Server No arquivo do Program.cs projeto, registre os seguintes serviços adicionais e


chame ConfigureCommonServices :

C#

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
...

builder.Services.AddRazorPages();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ServerAuthenticationStateProvider>();

Client.Program.ConfigureCommonServices(services);

Server No arquivo do Pages/_Host.cshtml projeto, substitua o Component Auxiliar de


Marcação ( <component ... /> ) pelo seguinte:

CSHTML

<div id="app">
@if (HttpContext.Request.Path.StartsWithSegments("/authentication"))
{
<component type="typeof({CLIENT APP ASSEMBLY NAME}.App)"
render-mode="WebAssembly" />
}
else
{
<component type="typeof({CLIENT APP ASSEMBLY NAME}.App)"
render-mode="WebAssemblyPrerendered" />
}
</div>

No exemplo anterior:

O espaço reservado {CLIENT APP ASSEMBLY NAME} é o nome do assembly do


aplicativo cliente (por exemplo BlazorSample.Client , ).
A verificação condicional para o segmento de /authentication caminho:
Evita a pré-geração ( render-mode="WebAssembly" ) para caminhos de
autenticação.
Pré-remetentes ( render-mode="WebAssemblyPrerendered" ) para caminhos de não
autenticação.

Opções para aplicativos hospedados e


provedores de logon de terceiros
Ao autenticar e autorizar um aplicativo hospedado Blazor WebAssembly com um
provedor de terceiros, há várias opções disponíveis para autenticar o usuário. Qual você
escolhe depende do seu cenário.

Para saber mais, confira Persistir declarações e tokens adicionais de provedores externos
no ASP.NET Core.
Autenticar usuários para chamar apenas APIs de terceiros
protegidas
Autentique o usuário com um fluxo OAuth do lado do cliente no provedor de API de
terceiros:

C#

builder.services.AddOidcAuthentication(options => { ... });

Neste cenário:

O servidor que hospeda o aplicativo não desempenha uma função.


As APIs no servidor não podem ser protegidas.
O aplicativo só pode chamar APIs de terceiros protegidas.

Autenticar usuários com um provedor de terceiros e


chamar APIs protegidas no servidor host e em terceiros
Configure Identity com um provedor de logon de terceiros. Obtenha os tokens
necessários para acesso à API de terceiros e armazene-os.

Quando um usuário faz logon, Identity coleta tokens de acesso e atualização como
parte do processo de autenticação. Nesse ponto, há algumas abordagens disponíveis
para fazer chamadas à API para APIs de terceiros.

Usar um token de acesso do servidor para recuperar o token de


acesso de terceiros

Use o token de acesso gerado no servidor para recuperar o token de acesso de terceiros
de um ponto de extremidade da API do servidor. A partir daí, use o token de acesso de
terceiros para chamar recursos de API de terceiros diretamente no Identity cliente.

Não recomendamos essa abordagem. Essa abordagem requer tratar o token de acesso
de terceiros como se ele tivesse sido gerado para um cliente público. Em termos OAuth,
o aplicativo público não tem um segredo do cliente porque não é confiável armazenar
segredos com segurança e o token de acesso é produzido para um cliente confidencial.
Um cliente confidencial é um cliente que tem um segredo do cliente e é considerado
capaz de armazenar segredos com segurança.

O token de acesso de terceiros pode receber escopos adicionais para executar


operações confidenciais com base no fato de que o terceiro emitiu o token para
um cliente mais confiável.
Da mesma forma, os tokens de atualização não devem ser emitidos para um
cliente que não é confiável, pois isso dá ao cliente acesso ilimitado, a menos que
outras restrições sejam colocadas em prática.

Fazer chamadas à API do cliente para a API do servidor para


chamar APIs de terceiros
Faça uma chamada à API do cliente para a API do servidor. No servidor, recupere o
token de acesso para o recurso de API de terceiros e emita qualquer chamada
necessária.

Embora essa abordagem exija um salto de rede extra por meio do servidor para chamar
uma API de terceiros, isso resulta em uma experiência mais segura:

O servidor pode armazenar tokens de atualização e garantir que o aplicativo não


perca o acesso a recursos de terceiros.
O aplicativo não pode vazar tokens de acesso do servidor que podem conter
permissões mais confidenciais.

Usar pontos de extremidade do OpenID


Connect (OIDC) v2.0
A biblioteca de autenticação e Blazor os modelos de projeto usam pontos de
extremidade OIDC (OpenID Connect) v1.0. Para usar um ponto de extremidade v2.0,
configure a opção Portador JwtBearerOptions.Authority JWT. No exemplo a seguir, o
AAD é configurado para v2.0 acrescentando um v2.0 segmento à Authority
propriedade :

C#

using Microsoft.AspNetCore.Authentication.JwtBearer;

...

builder.Services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme,
options =>
{
options.Authority += "/v2.0";
});
Como alternativa, a configuração pode ser feita no arquivo de configurações do
aplicativo ( appsettings.json ):

JSON

{
"Local": {
"Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
...
}
}

Se a abordagem em um segmento para a autoridade não for apropriada para o


provedor OIDC do aplicativo, como com provedores não AAD, defina a Authority
propriedade diretamente. Defina a propriedade no JwtBearerOptions ou no arquivo de
configurações do aplicativo ( appsettings.json ) com a Authority chave .

A lista de declarações no token de ID é alterada para pontos de extremidade v2.0. Para


obter mais informações, consulte Por que atualizar para plataforma de identidade da
Microsoft (v2.0)?.

Configurar e usar gRPC em componentes


Para configurar um Blazor WebAssembly aplicativo para usar o ASP.NET Core estrutura
gRPC:

Habilite gRPC-Web no servidor. Para obter mais informações, consulte gRPC-Web


em ASP.NET Core aplicativos gRPC.
Registre os serviços gRPC para o manipulador de mensagens do aplicativo. O
exemplo a seguir configura o manipulador de mensagens de autorização do
aplicativo para usar o GreeterClient serviço do tutorial gRPC ( Program.cs ):

C#

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using {APP ASSEMBLY}.Shared;

...

builder.Services.AddScoped(sp =>
{
var baseAddressMessageHandler =
sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
var grpcWebHandler =
new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
var channel =
GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress,
new GrpcChannelOptions { HttpHandler = grpcWebHandler });

return new Greeter.GreeterClient(channel);


});

O espaço reservado {APP ASSEMBLY} é o nome do assembly do aplicativo (por exemplo,


BlazorSample ). Coloque o .proto arquivo no Shared projeto da solução hospedada

Blazor .

Um componente no aplicativo cliente pode fazer chamadas gRPC usando o cliente gRPC
( Pages/Grpc.razor ):

razor

@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@using {APP ASSEMBLY}.Shared
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
<input @bind="name" placeholder="Type your name" />
<button @onclick="GetGreeting" class="btn btn-primary">Call gRPC
service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
private string name = "Bert";
private string serverResponse;

private async Task GetGreeting()


{
try
{
var request = new HelloRequest { Name = name };
var reply = await GreeterClient.SayHelloAsync(request);
serverResponse = reply.Message;
}
catch (Grpc.Core.RpcException ex)
when (ex.Status.DebugException is
AccessTokenNotAvailableException tokenEx)
{
tokenEx.Redirect();
}
}
}

O espaço reservado {APP ASSEMBLY} é o nome do assembly do aplicativo (por exemplo,


BlazorSample ). Para usar a propriedade , use Grpc.Net.Client a Status.DebugException
versão 2.30.0 ou posterior.

Para obter mais informações, consulte gRPC-Web em ASP.NET Core aplicativos gRPC.

Substituir a AuthenticationService
implementação
As subseções a seguir explicam como substituir:

Qualquer implementação de JavaScript AuthenticationService .


A Biblioteca de Autenticação do Microsoft para JavaScript ( MSAL.js ).

Substituir qualquer implementação de JavaScript


AuthenticationService

Crie uma biblioteca JavaScript para lidar com seus detalhes de autenticação
personalizada.

2 Aviso

As diretrizes nesta seção são um detalhe de implementação do padrão


RemoteAuthenticationService<TRemoteAuthenticationState,TAccount,TProvider
Options>. O código TypeScript nesta seção aplica-se especificamente ao ASP.NET
Core 7.0 e está sujeito a alterações sem aviso prévio em versões futuras do
ASP.NET Core.

TypeScript

// .NET makes calls to an AuthenticationService object in the Window.


declare global {
interface Window { AuthenticationService: AuthenticationService }
}

export interface AuthenticationService {


// Init is called to initialize the AuthenticationService.
public static init(settings: UserManagerSettings &
AuthorizeServiceSettings, logger: any) : Promise<void>;
// Gets the currently authenticated user.
public static getUser() : Promise<{[key: string] : string }>;

// Tries to get an access token silently.


public static getAccessToken(options: AccessTokenRequestOptions) :
Promise<AccessTokenResult>;

// Tries to sign in the user or get an access token interactively.


public static signIn(context: AuthenticationContext) :
Promise<AuthenticationResult>;

// Handles the sign-in process when a redirect is used.


public static async completeSignIn(url: string) :
Promise<AuthenticationResult>;

// Signs the user out.


public static signOut(context: AuthenticationContext) :
Promise<AuthenticationResult>;

// Handles the signout callback when a redirect is used.


public static async completeSignOut(url: string) :
Promise<AuthenticationResult>;
}

// The rest of these interfaces match their C# definitions.

export interface AccessTokenRequestOptions {


scopes: string[];
returnUrl: string;
}

export interface AccessTokenResult {


status: AccessTokenResultStatus;
token?: AccessToken;
}

export interface AccessToken {


value: string;
expires: Date;
grantedScopes: string[];
}

export enum AccessTokenResultStatus {


Success = 'Success',
RequiresRedirect = 'RequiresRedirect'
}

export enum AuthenticationResultStatus {


Redirect = 'Redirect',
Success = 'Success',
Failure = 'Failure',
OperationCompleted = 'OperationCompleted'
};
export interface AuthenticationResult {
status: AuthenticationResultStatus;
state?: unknown;
message?: string;
}

export interface AuthenticationContext {


state?: unknown;
interactiveRequest: InteractiveAuthenticationRequest;
}

export interface InteractiveAuthenticationRequest {


scopes?: string[];
additionalRequestParameters?: { [key: string]: any };
};

Você pode importar a biblioteca removendo a marca original <script> e adicionando


uma <script> marca que carrega a biblioteca personalizada. O exemplo a seguir
demonstra a substituição da marca padrão <script> por uma que carrega uma
biblioteca chamada CustomAuthenticationService.js da wwwroot/js pasta .

Dentro wwwroot/index.html da marca de fechamento </body> :

diff

- <script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>
+ <script src="js/CustomAuthenticationService.js"></script>

Para obter mais informações, consulte AuthenticationService.ts no dotnet/aspnetcore


repositório GitHub .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Substituir a Biblioteca de Autenticação do Microsoft para


JavaScript ( MSAL.js )
Se um aplicativo exigir uma versão personalizada da Biblioteca de Autenticação do
Microsoft para JavaScript (MSAL.js) , execute as seguintes etapas:

1. Confirme se o sistema tem o SDK do .NET do desenvolvedor mais recente ou


obtenha e instale o SDK do desenvolvedor mais recente do SDK do .NET Core:
instaladores e binários . A configuração de feeds internos do NuGet não é
necessária para esse cenário.
2. Configure o dotnet/aspnetcore repositório GitHub para desenvolvimento de
acordo com os documentos em Compilar ASP.NET Core da Origem . Crie fork e
clone ou baixe um arquivo ZIP do repositório GitHub dotnet/aspnetcore .
3. Abra o
src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json

arquivo e defina a versão desejada do @azure/msal-browser . Para obter uma lista


de versões lançadas, visite o site do @azure/msal-browser npm e selecione a
guia Versões .
4. Compile o Authentication.Msal projeto na
src/Components/WebAssembly/Authentication.Msal/src pasta com o yarn build

comando em um shell de comando.


5. Se o aplicativo usar ativos compactados (Brotli/Gzip), compacte o
Interop/dist/Release/AuthenticationService.js arquivo.
6. Copie o AuthenticationService.js arquivo e as versões compactadas ( .br .gz /)
do arquivo, se produzido, da Interop/dist/Release pasta para a pasta do
publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal aplicativo

nos ativos publicados do aplicativo.

Passar opções de provedor personalizado


Defina uma classe para passar os dados para a biblioteca JavaScript subjacente.

) Importante

A estrutura da classe deve corresponder ao que a biblioteca espera quando o JSON


é serializado com System.Text.Json.

O exemplo a seguir demonstra uma ProviderOptions classe com JsonPropertyName


atributos que correspondem às expectativas de uma biblioteca de provedores
personalizados hipotética:

C#
public class ProviderOptions
{
public string? Authority { get; set; }
public string? MetadataUrl { get; set; }

[JsonPropertyName("client_id")]
public string? ClientId { get; set; }

public IList<string> DefaultScopes { get; } =


new List<string> { "openid", "profile" };

[JsonPropertyName("redirect_uri")]
public string? RedirectUri { get; set; }

[JsonPropertyName("post_logout_redirect_uri")]
public string? PostLogoutRedirectUri { get; set; }

[JsonPropertyName("response_type")]
public string? ResponseType { get; set; }

[JsonPropertyName("response_mode")]
public string? ResponseMode { get; set; }
}

Registre as opções do provedor no sistema de DI e configure os valores apropriados:

C#

builder.Services.AddRemoteAuthentication<RemoteAuthenticationState,
RemoteUserAccount,
ProviderOptions>(options => {
options.Authority = "...";
options.MetadataUrl = "...";
options.ClientId = "...";
options.DefaultScopes = new List<string> { "openid", "profile",
"myApi" };
options.RedirectUri = "https://localhost:5001/authentication/login-
callback";
options.PostLogoutRedirectUri =
"https://localhost:5001/authentication/logout-callback";
options.ResponseType = "...";
options.ResponseMode = "...";
});

O exemplo anterior define URIs de redirecionamento com literais de cadeia de


caracteres regulares. As seguintes alternativas estão disponíveis:

TryCreate usando IWebAssemblyHostEnvironment.BaseAddress:

C#
Uri.TryCreate($"
{builder.HostEnvironment.BaseAddress}authentication/login-callback",
UriKind.Absolute, out var redirectUri);
options.RedirectUri = redirectUri;

Configuração do construtor de host:

C#

options.RedirectUri = builder.Configuration["RedirectUri"];

wwwroot/appsettings.json :

JSON

{
"RedirectUri": "https://localhost:5001/authentication/login-callback"
}

Recursos adicionais
Usar a API do Graph com o ASP.NET Core Blazor WebAssembly
HttpClient e HttpRequestMessage com as opções buscar solicitação de API
Grupos do AAD (Azure Active
Directory), Funções de Administrador e
Funções de Aplicativo
Artigo • 17/12/2022 • 16 minutos para o fim da leitura

Este artigo explica como configurar Blazor WebAssembly para usar grupos e funções do
Azure Active Directory.

O AAD (Azure Active Directory) fornece várias abordagens de autorização que podem
ser combinadas com ASP.NET Core Identity:

Grupos
Segurança
Microsoft 365
Distribuição
Funções
Funções de administrador do AAD
Funções de Aplicativo

As diretrizes neste artigo se aplicam aos cenários de implantação do Blazor


WebAssembly AAD descritos nos seguintes tópicos:

Aplicativo autônomo com contas Microsoft


Aplicativo autônomo com o AAD
Aplicativo hospedado com o AAD

As diretrizes do artigo fornecem instruções para aplicativos cliente e servidor:

CLIENTE: aplicativos autônomos Blazor WebAssembly ou o Client aplicativo de


uma solução hospedadaBlazor.
SERVIDOR: ASP.NET Core aplicativos de API web/API do servidor ou o Server
aplicativo de uma solução hospedadaBlazor. Você pode ignorar as diretrizes de
SERVIDOR ao longo do artigo para um aplicativo autônomo Blazor WebAssembly .

Os exemplos neste artigo aproveitam os recursos recentes do .NET lançados com o


ASP.NET Core 6.0 ou posterior. Ao usar os exemplos no ASP.NET Core 5.0, pequenas
modificações são necessárias. No entanto, os exemplos de texto e código referentes à
interação com o AAD e o Microsoft Graph são os mesmos para todas as versões do
ASP.NET Core.
Pré-requisito
As diretrizes neste artigo implementam a Microsoft API do Graph de acordo com as
diretrizes do SDK do Graph em Usar API do Graph com ASP.NET Core Blazor
WebAssembly. Siga as diretrizes de implementação do SDK do Graph para configurar o
aplicativo e testá-lo para confirmar se o aplicativo pode obter API do Graph dados para
uma conta de usuário de teste. Além disso, consulte o artigo de segurança do artigo API
do Graph links cruzados para examinar Microsoft conceitos de segurança do Graph.

Ao testar com o SDK do Graph localmente, recomendamos usar uma nova sessão de
navegador incógnito/privada para cada teste para evitar que os s persistentes
cookieinterfiram nos testes. Para obter mais informações, consulte Proteger um
aplicativo autônomo ASP.NET Core Blazor WebAssembly com o Azure Active Directory.

Escopos
Para permitir Microsoft API do Graph chamadas para dados de perfil de usuário,
atribuição de função e associação de grupo:

Um aplicativo CLIENTE é configurado com o User.Read escopo


( https://graph.microsoft.com/User.Read ) no portal do Azure.
Um aplicativo SERVER é configurado com o GroupMember.Read.All escopo
( https://graph.microsoft.com/GroupMember.Read.All ) no portal do Azure.

Os escopos anteriores são necessários além dos escopos necessários em cenários de


implantação do AAD descritos pelos tópicos listados anteriormente (Autônomo com
contas Microsoft, Autônomo com AAD e Hospedado com AAD).

Para obter mais informações, consulte a referência de permissões do Microsoft Graph.

7 Observação

As palavras "permission" e "scope" são usadas de forma intercambiável no portal


do Azure e em vários conjuntos de documentação Microsoft e externos. Este artigo
usa a palavra "escopo" para as permissões atribuídas a um aplicativo no portal do
Azure.

Atributo declarações de associação de grupo


No manifesto do aplicativo no portal do Azure para aplicativos CLIENT e SERVER, defina
o groupMembershipClaims atributo como All . Um valor de All resulta no envio de
todos os grupos de segurança, grupos de distribuição e funções do usuário conectado
na declaração de IDs conhecida (wids):

1. Abra o registro de portal do Azure do aplicativo.


2. Selecione Gerenciar>Manifesto na barra lateral.
3. Localize o groupMembershipClaims atributo .
4. Defina o valor como All ( "groupMembershipClaims": "All" ).
5. Selecione o botão Salvar.

Conta de usuário personalizada


Atribua usuários a grupos de segurança do AAD e funções de administrador do AAD no
portal do Azure.

Os exemplos neste artigo:

Suponha que um usuário seja atribuído à função administrador de cobrança do


AAD no locatário do AAD portal do Azure para autorização para acessar dados da
API do servidor.
Use políticas de autorização para controlar o acesso nos aplicativos CLIENT e
SERVER .

No aplicativo CLIENT , estenda RemoteUserAccount para incluir propriedades para:

Roles : matriz de Funções de Aplicativo do AAD (abordada na seção Funções de

Aplicativo )
Wids : Funções de administrador do AAD na declaração de IDs conhecidas (wids)

Oid : declaração do identificador de objeto imutável (oid) (identifica

exclusivamente um usuário dentro e entre locatários)

CustomUserAccount.cs :

C#

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount


{
[JsonPropertyName("roles")]
public List<string>? Roles { get; set; }

[JsonPropertyName("wids")]
public List<string>? Wids { get; set; }

[JsonPropertyName("oid")]
public string? Oid { get; set; }
}

Adicione uma referência de pacote ao aplicativo CLIENT para Microsoft.Graph .

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Adicione as classes e a configuração do utilitário do SDK do Graph nas diretrizes do SDK


do Graph do artigo Usar API do Graph com ASP.NET CoreBlazor WebAssembly.
Especifique o User.Read escopo do token de acesso, como mostra o artigo em seu
arquivo de exemplo wwwroot/appsettings.json .

Adicione a fábrica de contas de usuário personalizada a seguir ao aplicativo CLIENT . A


fábrica de usuários personalizada é usada para estabelecer:

Declarações de Função de Aplicativo ( appRole ) (abordadas na seção Funções de


Aplicativo ).
Declarações de função de administrador do AAD ( directoryRole ).
Exemplo de declarações de dados de perfil de usuário para o número de telefone
celular do usuário ( mobilePhone ) e o local do escritório ( officeLocation ).
Declarações do Grupo do AAD ( directoryGroup ).
Um ILogger ( logger ) para conveniência caso você deseje registrar informações ou
erros.

CustomAccountFactory.cs :

C#

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;

public class CustomAccountFactory


: AccountClaimsPrincipalFactory<CustomUserAccount>
{
private readonly ILogger<CustomAccountFactory> logger;
private readonly IServiceProvider serviceProvider;

public CustomAccountFactory(IAccessTokenProviderAccessor accessor,


IServiceProvider serviceProvider,
ILogger<CustomAccountFactory> logger)
: base(accessor)
{
this.serviceProvider = serviceProvider;
this.logger = logger;
}

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(


CustomUserAccount account,
RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);

if (initialUser.Identity is not null &&


initialUser.Identity.IsAuthenticated)
{
var userIdentity = initialUser.Identity as ClaimsIdentity;

if (userIdentity is not null)


{
account?.Roles?.ForEach((role) =>
{
userIdentity.AddClaim(new Claim("appRole", role));
});

account?.Wids?.ForEach((wid) =>
{
userIdentity.AddClaim(new Claim("directoryRole", wid));
});

try
{
var client = ActivatorUtilities
.CreateInstance<GraphServiceClient>
(serviceProvider);
var request = client.Me.Request();
var user = await request.GetAsync();

if (user is not null)


{
userIdentity.AddClaim(new Claim("mobilephone",
user.MobilePhone ?? "(000) 000-0000"));
userIdentity.AddClaim(new Claim("officelocation",
user.OfficeLocation ?? "Not set"));
}

var requestMemberOf =
client.Users[account?.Oid].MemberOf;
var memberships = await
requestMemberOf.Request().GetAsync();
if (memberships is not null)
{
foreach (var entry in memberships)
{
if (entry.ODataType == "#microsoft.graph.group")
{
userIdentity.AddClaim(
new Claim("directoryGroup", entry.Id));
}
}
}
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}

return initialUser;
}
}

O código anterior não inclui associações transitivas. Se o aplicativo exigir declarações de


associação de grupo diretas e transitivas, substitua a MemberOf propriedade
( IUserMemberOfCollectionWithReferencesRequestBuilder ) por TransitiveMemberOf
( IUserTransitiveMemberOfCollectionWithReferencesRequestBuilder ).

O código anterior ignora declarações de associação de grupo ( groups ) que são Funções
de Administrador do AAD ( #microsoft.graph.directoryRole tipo) porque os valores guid
retornados pelo plataforma de identidade da Microsoft são IDs de entidade de função
de administrador do AAD e não IDs de modelo de função. As IDs de entidade não são
estáveis entre locatários no plataforma de identidade da Microsoft e não devem ser
usadas para criar políticas de autorização para usuários em aplicativos. Sempre use IDs
de modelo de função para funções de administrador do AAD fornecidas por wids
declarações.

No aplicativo CLIENTE , configure a autenticação MSAL para usar o alocador de contas


de usuário personalizado.

Confirme se o Program.cs arquivo usa o


Microsoft.AspNetCore.Components.WebAssembly.Authentication namespace :

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
Atualize a AddMsalAuthentication chamada para o seguinte. Observe que o Blazor da
RemoteUserAccount estrutura é substituído pelo do CustomUserAccount aplicativo para a
fábrica de entidades de segurança de declarações de conta e autenticação da MSAL:

C#

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount,
CustomAccountFactory>();

Confirme a presença do código do SDK do Graph descrito pelo artigo Usar API do Graph
com ASP.NET Core Blazor WebAssembly e se a wwwroot/appsettings.json configuração
está correta de acordo com as diretrizes do SDK do Graph:

C#

var baseUrl = string.Join("/",


builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"],
builder.Configuration.GetSection("MicrosoftGraph")["Version"]);
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
.Get<List<string>>();

builder.Services.AddGraphClient(baseUrl, scopes);

wwwroot/appsettings.json :

JSON

"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com",
"Version: "v1.0",
"Scopes": [
"user.read"
]
}

Configuração de autorização
No aplicativo CLIENTE , crie uma política para cada Função de Aplicativo, Função de
Administrador do AAD ou grupo de segurança no Program.cs . O exemplo a seguir cria
uma política para a função administrador de cobrança do AAD:

C#

builder.Services.AddAuthorizationCore(options =>
{
options.AddPolicy("BillingAdministrator", policy =>
policy.RequireClaim("directoryRole",
"b0f54661-2d74-4c50-afa3-1ec803f12efe"));
});

Para obter a lista completa de IDs para funções de administrador do AAD, consulte IDs
de modelo de função na documentação do Azure. Para obter mais informações sobre
políticas de autorização, consulte Autorização baseada em políticas no ASP.NET Core.

Nos exemplos a seguir, o aplicativo CLIENT usa a política anterior para autorizar o
usuário.

O AuthorizeView componente funciona com a política:

razor

<AuthorizeView Policy="BillingAdministrator">
<Authorized>
<p>
The user is in the 'Billing Administrator' AAD Administrator
Role
and can see this content.
</p>
</Authorized>
<NotAuthorized>
<p>
The user is NOT in the 'Billing Administrator' role and sees
this
content.
</p>
</NotAuthorized>
</AuthorizeView>

O acesso a um componente inteiro pode ser baseado na política usando uma


[Authorize] diretiva de atributo (AuthorizeAttribute):

razor

@page "/"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "BillingAdministrator")]
Se o usuário não estiver autorizado, ele será redirecionado para a página de entrada do
AAD.

Uma verificação de política também pode ser executada no código com lógica de
procedimento.

Pages/CheckPolicy.razor :

razor

@page "/checkpolicy"
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<h1>Check Policy</h1>

<p>This component checks a policy in code.</p>

<button @onclick="CheckPolicy">Check 'BillingAdministrator' policy</button>

<p>Policy Message: @policyMessage</p>

@code {
private string policyMessage = "Check hasn't been made yet.";

[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }

private async Task CheckPolicy()


{
var user = (await authenticationStateTask).User;

if ((await AuthorizationService.AuthorizeAsync(user,
"BillingAdministrator")).Succeeded)
{
policyMessage = "Yes! The 'BillingAdministrator' policy is
met.";
}
else
{
policyMessage = "No! 'BillingAdministrator' policy is NOT met.";
}
}
}

Autorizar o acesso à API Web/API do servidor


Um aplicativo de API SERVER pode autorizar os usuários a acessar pontos de
extremidade de API seguros com políticas de autorização para grupos de segurança,
Funções de Administrador do AAD e Funções de Aplicativo quando um token de acesso
contém groups declarações , wids e role . O exemplo a seguir cria uma política para a
função administrador de cobrança do AAD ao Program.cs usar as wids declarações (IDs
conhecidas/IDs de modelo de função):

C#

builder.Services.AddAuthorization(options =>
{
options.AddPolicy("BillingAdministrator", policy =>
policy.RequireClaim("wids", "b0f54661-2d74-4c50-afa3-
1ec803f12efe"));
});

Para obter a lista completa de IDs para funções de administrador do AAD, consulte IDs
de modelo de função na documentação do Azure. Para obter mais informações sobre
políticas de autorização, consulte Autorização baseada em políticas no ASP.NET Core.

O acesso a um controlador no aplicativo SERVER pode ser baseado no uso de um


[Authorize] atributo com o nome da política (documentação da API: AuthorizeAttribute).

O exemplo a seguir limita o acesso aos dados de cobrança do para administradores


BillingDataController de cobrança do Azure com um nome de política de
BillingAdministrator :

C#

...
using Microsoft.AspNetCore.Authorization;

[Authorize(Policy = "BillingAdministrator")]
[ApiController]
[Route("[controller]")]
public class BillingDataController : ControllerBase
{
...
}

Para obter mais informações, consulte Autorização baseada em política no ASP.NET


Core.

Funções de Aplicativo
Para configurar o aplicativo no portal do Azure para fornecer declarações de associação
de funções de aplicativo, consulte Como adicionar funções de aplicativo em seu
aplicativo e recebê-las no token na documentação do Azure.

O exemplo a seguir pressupõe que os aplicativos CLIENT e SERVER estão configurados


com duas funções e as funções são atribuídas a um usuário de teste:

Admin
Developer

7 Observação

Ao desenvolver um aplicativo hospedado Blazor WebAssembly ou um par cliente-


servidor de aplicativos autônomos (um aplicativo autônomo Blazor WebAssembly e
um aplicativo de API web/API do servidor ASP.NET Core), a appRoles propriedade
de manifesto do cliente e do servidor portal do Azure registros de aplicativo deve
incluir as mesmas funções configuradas. Depois de estabelecer as funções no
manifesto do aplicativo cliente, copie-as em sua totalidade para o manifesto do
aplicativo de servidor. Se você não espelhar o manifesto appRoles entre os
registros de aplicativo do cliente e do servidor, as declarações de função não serão
estabelecidas para usuários autenticados da API do servidor/API Web, mesmo que
o token de acesso tenha as entradas corretas nas role declarações.

Embora não seja possível atribuir funções a grupos sem uma conta Azure AD Premium,
você pode atribuir funções aos usuários e receber uma role declaração para usuários
com uma conta padrão do Azure. As diretrizes nesta seção não exigem uma conta
premium do AAD.

Se você tiver uma conta do Azure de camada Premium, Gerenciar>funções de


aplicativo aparecerá na barra lateral portal do Azure registro do aplicativo. Siga as
diretrizes em Como adicionar funções de aplicativo em seu aplicativo e recebê-las no
token para configurar as funções do aplicativo.

Se você não tiver uma conta do Azure de camada Premium, edite o manifesto do
aplicativo no portal do Azure. Siga as diretrizes em Funções de aplicativo: funções
usando Azure AD Funções de Aplicativo: Implementação para estabelecer as funções do
aplicativo manualmente na appRoles entrada do arquivo de manifesto. Salve as
alterações no arquivo.

Veja a seguir uma entrada de exemplo appRoles que cria Admin e Developer funções.
Essas funções de exemplo são usadas posteriormente no exemplo desta seção no nível
do componente para implementar restrições de acesso:

JSON
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"description": "Administrators manage developers.",
"displayName": "Admin",
"id": "584e483a-7101-404b-9bb1-83bf9463e335",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Admin"
},
{
"allowedMemberTypes": [
"User"
],
"description": "Developers write code.",
"displayName": "Developer",
"id": "82770d35-2a93-4182-b3f5-3d7bfe9dfe46",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Developer"
}
],

7 Observação

Você pode gerar GUIDs com um programa gerador guid online (resultado da
pesquisa do Google para "gerador guid") .

Para atribuir uma função a um usuário (ou grupo, se você tiver uma conta do Azure de
camada Premium):

1. Navegue até Aplicativos empresariais na área do AAD do portal do Azure.


2. Selecione o aplicativo. Selecione Gerenciar>Usuários e grupos na barra lateral.
3. Marque a caixa de seleção para uma ou mais contas de usuário.
4. No menu acima da lista de usuários, selecione Editar atribuição.
5. Para a entrada Selecionar uma função , selecione Nenhum selecionado.
6. Escolha uma função na lista e use o botão Selecionar para selecioná-la.
7. Use o botão Atribuir na parte inferior da tela para atribuir a função.

Várias funções são atribuídas no portal do Azure adicionando novamente um usuário


para cada atribuição de função adicional. Use o botão Adicionar usuário/grupo na
parte superior da lista de usuários para adicionar novamente um usuário. Use as etapas
anteriores para atribuir outra função ao usuário. Você pode repetir esse processo
quantas vezes forem necessárias para adicionar funções adicionais a um usuário (ou
grupo).

O CustomAccountFactory mostrado na seção Conta de usuário personalizada é


configurado para agir em uma role declaração com um JSvalor de matriz ON. Adicione
e registre o CustomAccountFactory no aplicativo CLIENT , conforme mostrado na seção
Conta de usuário personalizada . Não é necessário fornecer código para remover a
declaração original role porque ela é removida automaticamente pela estrutura.

Em Program.cs um aplicativo CLIENT , especifique a declaração chamada " appRole "


como a declaração de função para ClaimsPrincipal.IsInRole verificações:

C#

builder.Services.AddMsalAuthentication(options =>
{
...

options.UserOptions.RoleClaim = "appRole";
});

7 Observação

Se você preferir usar a directoryRoles declaração (ADD Administrator Roles),


atribua " directoryRoles " ao RemoteAuthenticationUserOptions.RoleClaim.

Em Program.cs um aplicativo SERVER , especifique a declaração chamada


" http://schemas.microsoft.com/ws/2008/06/identity/claims/role " como a declaração
de função para ClaimsPrincipal.IsInRole verificações:

C#

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
Configuration.Bind("AzureAd", options);
options.TokenValidationParameters.RoleClaimType =
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
},
options => { Configuration.Bind("AzureAd", options); });

7 Observação
Quando um único esquema de autenticação é registrado, o esquema de
autenticação é usado automaticamente como o esquema padrão do aplicativo e
não é necessário declarar o esquema para AddAuthentication ou por meio
AuthenticationOptionsde . Para obter mais informações, consulte Visão geral da
Autenticação ASP.NET Core e o comunicado de ASP.NET Core
(aspnet/Announcements #490) .

7 Observação

Se você preferir usar a wids declaração (ADD Administrator Roles), atribua " wids "
ao TokenValidationParameters.RoleClaimType.

Depois de concluir as etapas anteriores para criar e atribuir funções a usuários (ou
grupos, se você tiver uma conta do Azure de camada Premium) e implementar o com o
CustomAccountFactory SDK do Graph, conforme explicado anteriormente neste artigo e
em Usar API do Graph com ASP.NET Core Blazor WebAssembly, você deverá ver uma
declaração appRole para cada função atribuída à qual um usuário conectado é atribuído
(ou funções atribuídas a grupos dos quais eles são membros). Execute o aplicativo com
um usuário de teste para confirmar se as declarações estão presentes conforme o
esperado. Ao testar com o SDK do Graph localmente, recomendamos usar uma nova
sessão do navegador incognito/privado para cada teste para evitar que os persistentes
cookieinterfiram nos testes. Para obter mais informações, consulte Proteger um
aplicativo autônomo ASP.NET Core Blazor WebAssembly com o Azure Active Directory.

As abordagens de autorização de componentes estão funcionais neste momento.


Qualquer um dos mecanismos de autorização em componentes do aplicativo CLIENT
pode usar a Admin função para autorizar o usuário:

AuthorizeView Componente

razor

<AuthorizeView Roles="Admin">

[Authorize] diretiva de atributo (AuthorizeAttribute)

razor

@attribute [Authorize(Roles = "Admin")]

Lógica de procedimento
C#

var authState = await


AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;

if (user.IsInRole("Admin")) { ... }

Há suporte para vários testes de função:

Exija que o usuário esteja na Admin função ou Developer com o AuthorizeView


componente :

razor

<AuthorizeView Roles="Admin, Developer">


...
</AuthorizeView>

Exija que o usuário esteja nas Admin funções e Developer com o AuthorizeView
componente :

razor

<AuthorizeView Roles="Admin">
<AuthorizeView Roles="Developer">
...
</AuthorizeView>
</AuthorizeView>

Exija que o usuário esteja na Admin função ou Developer com o [Authorize]


atributo :

razor

@attribute [Authorize(Roles = "Admin, Developer")]

Exija que o usuário esteja nas Admin funções e Developer com o [Authorize]
atributo :

razor

@attribute [Authorize(Roles = "Admin")]


@attribute [Authorize(Roles = "Developer")]
Exija que o usuário esteja na funçãoou Developer com código Admin de
procedimento:

razor

@code {
private async Task DoSomething()
{
var authState = await AuthenticationStateProvider
.GetAuthenticationStateAsync();
var user = authState.User;

if (user.IsInRole("Admin") || user.IsInRole("Developer"))
{
...
}
else
{
...
}
}
}

Exija que o usuário esteja nas Admin funções e Developer com código de
procedimento alterando o OR condicional (||) para um AND condicional (&&) no
exemplo anterior:

C#

if (user.IsInRole("Admin") && user.IsInRole("Developer"))

Qualquer um dos mecanismos de autorização nos controladores do aplicativo SERVER


pode usar a Admin função para autorizar o usuário:

[Authorize] diretiva de atributo (AuthorizeAttribute)

C#

[Authorize(Roles = "Admin")]

Lógica de procedimento

C#

if (User.IsInRole("Admin")) { ... }

Há suporte para vários testes de função:


Exija que o usuário esteja na Admin função ou Developer com o [Authorize]
atributo :

C#

[Authorize(Roles = "Admin, Developer")]

Exija que o usuário esteja nas Admin funções e Developer com o [Authorize]
atributo :

C#

[Authorize(Roles = "Admin")]
[Authorize(Roles = "Developer")]

Exija que o usuário esteja na funçãoou Developer com código Admin de


procedimento:

C#

static readonly string[] scopeRequiredByApi = new string[] {


"API.Access" };

...

[HttpGet]
public IEnumerable<ReturnType> Get()
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

if (User.IsInRole("Admin") || User.IsInRole("Developer"))
{
...
}
else
{
...
}

return ...
}

Exija que o usuário esteja nas Admin funções e Developer com código de
procedimento alterando o OR condicional (||) para um AND condicional (&&) no
exemplo anterior:

C#
if (User.IsInRole("Admin") && User.IsInRole("Developer"))

Como as comparações de cadeia de caracteres do .NET diferenciam maiúsculas de


minúsculas por padrão, os nomes de função correspondentes também diferenciam
maiúsculas de minúsculas. Por exemplo, Admin (maiúsculas A ) não é tratado como a
mesma função admin que (minúscula ). a

O caso Pascal normalmente é usado para nomes de função (por exemplo,


BillingAdministrator ), mas o uso do caso Pascal não é um requisito estrito. Diferentes

esquemas de uso de maiúsculas e minúsculas, como maiúsculas e minúsculas de kebab


e maiúsculas e minúsculas de cobra, são permitidos. O uso de espaços em nomes de
função também é incomum, mas permitido. Por exemplo, billing administrator é um
formato de nome de função incomum em aplicativos .NET, mas válido.

Recursos adicionais
IDs de modelo de função (documentação do Azure)
groupMembershipClaims atributo (documentação do Azure)
Como adicionar funções de aplicativo em seu aplicativo e recebê-las no token
(documentação do Azure)
Funções de aplicativo (documentação do Azure)
Autorização baseada em declarações no ASP.NET Core
Autorização baseada em função no ASP.NET Core
Autenticação e autorização de Blazor no ASP.NET Core
Usar a API do Graph com o ASP.NET
Core Blazor WebAssembly
Artigo • 14/12/2022 • 16 minutos para o fim da leitura

Este artigo explica como usar Microsoft API do Graph em Blazor WebAssembly
aplicativos, que é uma RESTAPI Web completa que permite que os aplicativos acessem
Microsoft recursos do serviço de nuvem.

Duas abordagens estão disponíveis para interagir diretamente com o Microsoft Graph
em Blazor aplicativos:

SDK do Graph: os SDKs do Microsoft Graph foram projetados para simplificar a


criação de aplicativos de alta qualidade, eficientes e resilientes que acessam
Microsoft Graph. Selecione o botão SDK do Graph na parte superior deste artigo
para adotar essa abordagem.

HttpClient nomeado com API do Graph: um nomeado HttpClient pode emitir


solicitações de API Web diretamente para API do Graph. Selecione o botão
HttpClient nomeado com API do Graph na parte superior deste artigo para adotar
essa abordagem.

As diretrizes neste artigo não se destinam a substituir a documentação principal do


Microsoft Graph e diretrizes adicionais de segurança do Azure em outros conjuntos de
documentação do Microsoft. Avalie as diretrizes de segurança na seção Recursos
adicionais deste artigo antes de implementar Microsoft Graph em um ambiente de
produção. Siga todas as práticas recomendadas do Microsoft para limitar a área da
superfície de ataque de seus aplicativos.

) Importante

Os cenários descritos neste artigo se aplicam ao uso do AAD (Azure Active


Directory) como o provedor de identidade, não o AAD B2C. No momento, não há
suporte para o uso do Microsoft Graph com um aplicativo do lado Blazor
WebAssembly do cliente e o provedor de identidade do AAD B2C.

Há suporte para o uso de um aplicativo hospedado Blazor WebAssembly , em que o


Server aplicativo usa o SDK/API do Graph para fornecer dados do Client Graph ao
aplicativo por meio da API Web. Para obter mais informações, consulte a seção Soluções
hospedadas Blazor WebAssembly deste artigo.
Os exemplos neste artigo aproveitam os recursos recentes do .NET lançados com o
ASP.NET Core 6.0 ou posterior. Ao usar os exemplos no ASP.NET Core 5.0 ou anterior,
pequenas modificações são necessárias. No entanto, os exemplos de texto e código
referentes à interação com Microsoft Graph são os mesmos para todas as versões do
ASP.NET Core.

Soluções hospedadas Blazor WebAssembly


Os exemplos neste artigo referem-se ao uso do SDK do Graph ou de um nomeado
HttpClient com API do Graph diretamente de um aplicativo autônomo Blazor

WebAssembly ou diretamente do Client aplicativo de uma solução hospedadaBlazor


WebAssembly. Um cenário adicional que não é abordado por este artigo é que um
Client aplicativo de uma solução hospedada chame o Server aplicativo da solução por
meio da API Web e, em seguida, o Server aplicativo usa o SDK/API do Graph para
chamar Microsoft Graph e retornar dados para o Client aplicativo. Embora essa seja uma
abordagem com suporte, ela não é abordada por este artigo. Se você quiser adotar essa
abordagem:

Siga as diretrizes em Chamar uma API Web de um aplicativo ASP.NET Core Blazor
para os aspectos da API Web sobre como emitir solicitações para o Server
aplicativo do Client aplicativo e retornar dados para o Client aplicativo.
Siga as diretrizes na documentação principal do Microsoft Graph para usar o SDK
do Graph com um aplicativo de ASP.NET Core típico, que nesse cenário é o Server
aplicativo da solução. Se você usar o Blazor WebAssembly modelo de projeto para
criar a solução hospedada Blazor WebAssembly (ASP.NET Core Hospedado/ -h|--
hosted ) com autorização organizacional (organização única/ SingleOrg ou várias

organizações/ MultiOrg ) e a opção Microsoft Graph (plataforma de identidade da


Microsoft>Connected Services) >Adicione Microsoft permissões do Graph no
Visual Studio ou a opção --calls-graph com o comando da CLI dotnet new do
.NET), o Server aplicativo da solução é configurado para usar o SDK do Graph
quando a solução é criada com base no modelo de projeto.

Recursos adicionais

Orientação geral
Documentação do Microsoft Graph
aplicativo de exemplo Microsoft GraphBlazor WebAssembly : este exemplo
demonstra como usar o SDK do .NET do Microsoft Graph para acessar dados em
Office 365 de Blazor WebAssembly aplicativos.
Criar aplicativos .NET com o tutorial do Microsoft Graph e Microsoft aplicativo
ASP.NET Core de exemplo do Graph: esses recursos são mais apropriados para
soluções hospedadasBlazor WebAssembly, em que o Server aplicativo está
configurado para acessar Microsoft Graph como um aplicativo ASP.NET Core típico
em nome do Client aplicativo. O Client aplicativo usa a API Web para fazer
solicitações ao aplicativo para dados do Server Graph. Embora esses recursos não
se apliquem diretamente à chamada do Graph de aplicativos do ladoBlazor
WebAssembly do cliente, a configuração do aplicativo AAD e as práticas de
codificação do Microsoft Graph nos recursos vinculados são relevantes para
aplicativos autônomos Blazor WebAssembly e devem ser consultadas para práticas
recomendadas gerais.

Diretrizes de segurança
Visão geral da autenticação do Microsoft Graph
Visão geral das permissões do Microsoft Graph
Referência de permissões do Microsoft Graph
Aumente a segurança com o princípio de privilégios mínimos
Práticas recomendadas de segurança do Microsoft: protegendo o acesso
privilegiado
Artigos sobre escalonamento de privilégios do Azure na Internet (resultado da
pesquisa do Google)
Impor uma política de segurança de
conteúdo para ASP.NET Core Blazor
Artigo • 28/11/2022 • 27 minutos para o fim da leitura

Este artigo explica como usar uma política de segurança de conteúdo (CSP) com
aplicativos ASP.NET Core Blazor para ajudar a proteger contra ataques de XSS (script
entre sites).

O XSS (Cross-Site Scripting) é uma vulnerabilidade de segurança em que um invasor


coloca um ou mais scripts mal-intencionados do lado do cliente no conteúdo
renderizado de um aplicativo. Um CSP ajuda a proteger contra ataques XSS informando
o navegador de válido:

Fontes para conteúdo carregado, incluindo scripts, folhas de estilos e imagens.


Ações executadas por uma página, especificando destinos de URL permitidos de
formulários.
Plug-ins que podem ser carregados.

Para aplicar um CSP a um aplicativo, o desenvolvedor especifica várias diretivas de


segurança de conteúdo CSP em um ou mais Content-Security-Policy cabeçalhos ou
<meta> marcas. Para obter diretrizes sobre como aplicar um CSP a um aplicativo no
código C# na inicialização, consulte ASP.NET Core Blazor inicialização.

As políticas são avaliadas pelo navegador enquanto uma página está sendo carregada.
O navegador inspeciona as fontes da página e determina se elas atendem aos requisitos
das diretivas de segurança de conteúdo. Quando as diretivas de política não são
atendidas para um recurso, o navegador não carrega o recurso. Por exemplo, considere
uma política que não permita scripts de terceiros. Quando uma página contém uma
<script> marca com uma origem src de terceiros no atributo , o navegador impede
que o script seja carregado.

O CSP tem suporte na maioria dos navegadores móveis e desktop modernos, incluindo
Chrome, Edge, Firefox, Opera e Safari. O CSP é recomendado para Blazor aplicativos.

Diretivas de política
No mínimo, especifique as seguintes diretivas e fontes para Blazor aplicativos. Adicione
diretivas e fontes adicionais conforme necessário. As seguintes diretivas são usadas na
seção Aplicar a política deste artigo, em que são fornecidas políticas de segurança de
exemplo para Blazor WebAssembly e Blazor Server :
base-uri : restringe as URLs para a marca de <base> uma página. Especifique
self para indicar que a origem do aplicativo, incluindo o esquema e o número da
porta, é uma origem válida.
default-src : indica um fallback para diretivas de origem que não são
especificadas explicitamente pela política. Especifique self para indicar que a
origem do aplicativo, incluindo o esquema e o número da porta, é uma origem
válida.
img-src : indica fontes válidas para imagens.
Especifique data: para permitir o carregamento de imagens de data: URLs.
Especifique https: para permitir o carregamento de imagens de pontos de
extremidade HTTPS.
object-src : indica fontes válidas para as <object> marcas , <embed> e <applet> .
Especifique none para impedir todas as fontes de URL.
script-src : indica fontes válidas para scripts.
Especifique self para indicar que a origem do aplicativo, incluindo o esquema
e o número da porta, é uma origem válida.
Em um Blazor WebAssembly aplicativo:
Especifique unsafe-eval para permitir que o Blazor WebAssembly runtime
mono funcione.
Especifique quaisquer hashes adicionais para permitir que os scripts não
estrutura necessários sejam carregados .
Em um Blazor Server aplicativo, especifique hashes para permitir que os scripts
necessários sejam carregados.
style-src : indica fontes válidas para folhas de estilos.
Especifique self para indicar que a origem do aplicativo, incluindo o esquema
e o número da porta, é uma origem válida.
Se o aplicativo usar estilos embutidos, especifique unsafe-inline para permitir
o uso de seus estilos embutidos.
upgrade-insecure-requests : indica que as URLs de conteúdo de fontes não
seguras (HTTP) devem ser adquiridas com segurança por HTTPS.

As diretivas anteriores têm suporte de todos os navegadores, exceto o Microsoft


Internet Explorer.

Para obter hashes SHA para scripts embutidos adicionais:

Aplique o CSP mostrado na seção Aplicar a política .


Acesse o console de ferramentas de desenvolvedor do navegador durante a
execução do aplicativo localmente. O navegador calcula e exibe hashes para
scripts bloqueados quando um cabeçalho ou meta marca CSP está presente.
Copie os hashes fornecidos pelo navegador para as script-src fontes. Use aspas
simples em torno de cada hash.

Para obter uma matriz de suporte do navegador Nível 2 da Política de Segurança de


Conteúdo, consulte Posso usar: Política de Segurança de Conteúdo Nível 2 .

Aplicar a política
Use uma <meta> marca para aplicar a política:

Defina o valor do http-equiv atributo como Content-Security-Policy .


Coloque as diretivas no valor do content atributo. Diretivas separadas com ponto
e vírgula ( ; ).
Sempre coloque a meta marca no <head> conteúdo.

As seções a seguir mostram políticas de exemplo para Blazor WebAssembly e Blazor


Server. Esses exemplos têm controle de versão neste artigo para cada versão do Blazor.
Para usar uma versão apropriada para sua versão, selecione a versão do documento
com o seletor suspenso Versão nesta página da Web.

Blazor WebAssembly
<head> No conteúdo da página do wwwroot/index.html host, aplique as diretivas

descritas na seção Diretivas de política:

HTML

<meta http-equiv="Content-Security-Policy"
content="base-uri 'self';
default-src 'self';
img-src data: https:;
object-src 'none';
script-src 'self'
'unsafe-eval';
style-src 'self';
upgrade-insecure-requests;">

Adicione hashes e style-src adicionais script-src conforme exigido pelo aplicativo.


Durante o desenvolvimento, use uma ferramenta online ou ferramentas de
desenvolvedor de navegador para ter os hashes calculados para você. Por exemplo, o
seguinte erro do console de ferramentas do navegador relata o hash de um script
necessário não coberto pela política:
Recusou-se a executar o script embutido porque viola a seguinte diretiva de Política
de Segurança de Conteúdo: " ... ". A palavra-chave 'unsafe-inline', um hash ('sha256-
v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA=') ou um nonce
('nonce-...') é necessário para habilitar a execução embutida.

O script específico associado ao erro é exibido no console ao lado do erro.

Blazor Server
Na marcação <head> da página host (local do <head> conteúdo), aplique as diretivas
descritas na seção Diretivas de política :

CSHTML

<meta http-equiv="Content-Security-Policy"
content="base-uri 'self';
default-src 'self';
img-src data: https:;
object-src 'none';
script-src 'self';
style-src 'self';
upgrade-insecure-requests;">

Adicione hashes e style-src adicionais script-src conforme exigido pelo aplicativo.


Durante o desenvolvimento, use uma ferramenta online ou ferramentas de
desenvolvedor de navegador para ter os hashes calculados para você. Por exemplo, o
seguinte erro do console de ferramentas do navegador relata o hash de um script
necessário não coberto pela política:

Recusou-se a executar o script embutido porque viola a seguinte diretiva de Política


de Segurança de Conteúdo: " ... ". A palavra-chave 'unsafe-inline', um hash ('sha256-
v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA=') ou um nonce
('nonce-...') é necessário para habilitar a execução embutida.

O script específico associado ao erro é exibido no console ao lado do erro.

Limitações de metadados
Uma <meta> política de marca não dá suporte às seguintes diretivas:

frame-ancestors
report-to
report-uri
Sandbox

Para dar suporte às diretivas anteriores, use um cabeçalho chamado Content-Security-


Policy . A cadeia de caracteres de diretiva é o valor do cabeçalho.

Testar uma política e receber relatórios de


violação
O teste ajuda a confirmar que os scripts de terceiros não são bloqueados
inadvertidamente ao criar uma política inicial.

Para testar uma política durante um período de tempo sem impor as diretivas de
política, defina o atributo da marca ou o <meta> nome do http-equiv cabeçalho de uma
política baseada em cabeçalho como Content-Security-Policy-Report-Only . Os
relatórios de falha são enviados como JSdocumentos ON para uma URL especificada.
Para obter mais informações, consulte Documentos da Web do MDN: Content-Security-
Policy-Report-Only .

Para relatar violações enquanto uma política está ativa, consulte os seguintes artigos:

report-to
report-uri

Embora report-uri não seja mais recomendado para uso, ambas as diretivas devem ser
usadas até report-to que seja compatível com todos os principais navegadores. Não
use report-uri exclusivamente porque o suporte para report-uri está sujeito a ser
descartado a qualquer momento dos navegadores. Remova o suporte para report-uri
em suas políticas quando report-to houver suporte total. Para acompanhar a adoção
do report-to , consulte Posso usar: report-to .

Teste e atualize a política de um aplicativo a cada versão.

Solucionar problemas
Os erros aparecem no console de ferramentas para desenvolvedores do
navegador. Os navegadores fornecem informações sobre:
Elementos que não estão em conformidade com a política.
Como modificar a política para permitir um item bloqueado.
Uma política só é completamente eficaz quando o navegador do cliente dá
suporte a todas as diretivas incluídas. Para obter uma matriz de suporte do
navegador atual, consulte Posso usar: Content-Security-Policy .

Recursos adicionais
Aplicar um CSP no código C# na inicialização
Documentos da Web do MDN: Content-Security-Policy
Nível 2 da Política de Segurança de Conteúdo
Avaliador do CSP do Google
Blazor gerenciamento de estado
ASP.NET Core
Artigo • 28/11/2022 • 88 minutos para o fim da leitura

Este artigo descreve abordagens comuns para manter os dados de um usuário (estado)
enquanto eles usam um aplicativo e entre sessões do navegador.

Blazor Server é uma estrutura de aplicativo com estado. Na maioria das vezes, o
aplicativo mantém uma conexão com o servidor. O estado do usuário é mantido na
memória do servidor em um circuito.

Exemplos de estado do usuário mantidos em um circuito incluem:

A hierarquia de instâncias de componente e sua saída de renderização mais


recente na interface do usuário renderizada.
Os valores de campos e propriedades em instâncias de componente.
Dados mantidos em instâncias de serviço de DI (injeção de dependência) que têm
como escopo o circuito.

O estado do usuário também pode ser encontrado em variáveis JavaScript no conjunto


de memória do navegador por meio de chamadas de interoperabilidade javaScript .

Se um usuário tiver uma perda temporária de conexão de rede, Blazor tentará


reconectar o usuário ao circuito original com seu estado original. No entanto,
reconectar um usuário ao circuito original na memória do servidor nem sempre é
possível:

O servidor não pode reter um circuito desconectado para sempre. O servidor deve
liberar um circuito desconectado após um tempo limite ou quando o servidor
estiver sob pressão de memória.
Em ambientes de implantação com balanceamento de carga de vários servidores,
os servidores individuais podem falhar ou ser removidos automaticamente quando
não forem mais necessários para lidar com o volume geral de solicitações. As
solicitações de processamento do servidor original para um usuário podem ficar
indisponíveis quando o usuário tenta se reconectar.
O usuário pode fechar e reabrir o navegador ou recarregar a página, o que remove
qualquer estado mantido na memória do navegador. Por exemplo, os valores de
variável JavaScript definidos por meio de chamadas de interoperabilidade
javaScript são perdidos.
Quando um usuário não pode ser reconectado ao circuito original, o usuário recebe um
novo circuito com um estado vazio. Isso é equivalente a fechar e reabrir um aplicativo
da área de trabalho.

Manter o estado entre circuitos


Em geral, mantenha o estado entre circuitos em que os usuários estão criando dados
ativamente, não simplesmente lendo dados que já existem.

Para preservar o estado entre circuitos, o aplicativo deve manter os dados em algum
outro local de armazenamento que não seja a memória do servidor. A persistência de
estado não é automática. Você deve executar etapas ao desenvolver o aplicativo para
implementar a persistência de dados com estado.

A persistência de dados normalmente só é necessária para o estado de alto valor que os


usuários gastaram esforço para criar. Nos exemplos a seguir, manter o estado
economiza tempo ou ajuda em atividades comerciais:

Formulários da Web de várias etapas: é demorado para um usuário inserir dados


novamente para várias etapas concluídas de um formulário da Web de várias
etapas se seu estado for perdido. Um usuário perderá o estado nesse cenário se
sair do formulário e retornar mais tarde.
Carrinhos de compras: qualquer componente comercialmente importante de um
aplicativo que representa uma receita potencial pode ser mantido. Um usuário que
perde seu estado e, portanto, seu carrinho de compras, pode comprar menos
produtos ou serviços quando retornar ao site mais tarde.

Um aplicativo só pode persistir o estado do aplicativo. As interfaces do usuário não


podem ser persistidas, como instâncias de componente e suas árvores de renderização.
Componentes e árvores de renderização geralmente não são serializáveis. Para persistir
o estado da interface do usuário, como os nós expandidos de um controle de exibição
de árvore, o aplicativo deve usar código personalizado para modelar o comportamento
do estado da interface do usuário como estado serializável do aplicativo.

Onde persistir o estado


Existem locais comuns para manter o estado:

Armazenamento do lado do servidor


URL
Armazenamento do navegador
Serviço de contêiner de estado na memória
Armazenamento do lado do servidor
Para persistência de dados permanente que abrange vários usuários e dispositivos, o
aplicativo pode usar o armazenamento do lado do servidor. As opções incluem:

Armazenamento de blob
Armazenamento de chave-valor
Banco de dados relacional
Armazenamento de tabela

Depois que os dados são salvos, o estado do usuário é mantido e disponível em


qualquer novo circuito.

Para obter mais informações sobre as opções de armazenamento de dados do Azure,


confira o seguinte:

Bancos de dados do Azure


Documentação do Armazenamento do Azure

URL
Para dados transitórios que representam o estado de navegação, modele os dados
como parte da URL. Exemplos de estado do usuário modelados na URL incluem:

A ID de uma entidade exibida.


O número da página atual em uma grade paginada.

O conteúdo da barra de endereços do navegador é mantido:

Se o usuário recarregar manualmente a página.


Se o servidor Web ficar indisponível e o usuário for forçado a recarregar a página
para se conectar a um servidor diferente.

Para obter informações sobre como definir padrões de URL com a @page diretiva ,
consulte ASP.NET Core Blazor roteamento e navegação.

Armazenamento do navegador
Para dados transitórios que o usuário está criando ativamente, um local de
armazenamento comumente usado são as coleções e sessionStorage do
localStorage navegador:
localStorage tem como escopo a janela do navegador. Se o usuário recarregar a

página ou fechar e abrir novamente o navegador, o estado persistirá. Se o usuário


abrir várias guias do navegador, o estado será compartilhado entre as guias. Os
dados persistem até localStorage serem explicitamente limpos.
sessionStorage tem como escopo a guia do navegador. Se o usuário recarregar a

guia, o estado persistirá. Se o usuário fechar a guia ou o navegador, o estado será


perdido. Se o usuário abrir várias guias do navegador, cada guia terá sua própria
versão independente dos dados.

Em geral, sessionStorage é mais seguro de usar. sessionStorage evita o risco de um


usuário abrir várias guias e encontrar o seguinte:

Bugs no armazenamento de estado entre guias.


Comportamento confuso quando uma guia substitui o estado de outras guias.

localStorage será a melhor opção se o aplicativo precisar persistir o estado ao fechar e

reabrir o navegador.

Advertências para usar o armazenamento do navegador:

Semelhante ao uso de um banco de dados do lado do servidor, carregar e salvar


dados é assíncrono.
Ao contrário de um banco de dados do lado do servidor, o armazenamento não
está disponível durante a pré-geração porque a página solicitada não existe no
navegador durante o estágio de pré-geração.
O armazenamento de alguns quilobytes de dados é razoável para persistir para
Blazor Server aplicativos. Além de alguns quilobytes, você deve considerar as
implicações de desempenho porque os dados são carregados e salvos em toda a
rede.
Os usuários podem exibir ou adulterar os dados. ASP.NET Core Proteção de Dados
pode atenuar o risco. Por exemplo, ASP.NET Core Armazenamento de Navegador
Protegido usa ASP.NET Core Proteção de Dados.

Pacotes NuGet de terceiros fornecem APIs para trabalhar com localStorage e


sessionStorage . Vale a pena considerar a escolha de um pacote que usa de forma
transparente ASP.NET Core Proteção de Dados. A Proteção de Dados criptografa os
dados armazenados e reduz o risco potencial de adulteração de dados armazenados. Se
JSos dados serializados on forem armazenados em texto sem formatação, os usuários
poderão ver os dados usando ferramentas de desenvolvedor do navegador e também
modificar os dados armazenados. Proteger dados nem sempre é um problema porque
os dados podem ser triviais por natureza. Por exemplo, ler ou modificar a cor
armazenada de um elemento de interface do usuário não é um risco de segurança
significativo para o usuário ou para a organização. Evite permitir que os usuários
inspecionem ou violem dados confidenciais.

ASP.NET Core Armazenamento de Navegador


Protegido
ASP.NET Core Armazenamento de Navegador Protegido aproveita ASP.NET Core
Proteção de Dados para localStorage e sessionStorage .

7 Observação

O Armazenamento de Navegador Protegido depende da Proteção de Dados do


ASP.NET Core e só tem suporte para Blazor Server aplicativos.

Salvar e carregar dados dentro de um componente


Em qualquer componente que exija carregar ou salvar dados no armazenamento do
navegador, use a @inject diretiva para injetar uma instância de um dos seguintes:

ProtectedLocalStorage

ProtectedSessionStorage

A escolha depende do local de armazenamento do navegador que você deseja usar. No


exemplo a seguir, sessionStorage é usado:

razor

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

A @using diretiva pode ser colocada no arquivo do _Imports.razor aplicativo em vez de


no componente . O uso do _Imports.razor arquivo disponibiliza o namespace para
segmentos maiores do aplicativo ou de todo o aplicativo.

Para manter o currentCount valor no Counter componente de um aplicativo com base


no modelo de Blazor Server projeto, modifique o IncrementCount método para usar
ProtectedSessionStore.SetAsync :

C#
private async Task IncrementCount()
{
currentCount++;
await ProtectedSessionStore.SetAsync("count", currentCount);
}

Em aplicativos maiores e mais realistas, o armazenamento de campos individuais é um


cenário improvável. Aplicativos são mais propensos a armazenar objetos de modelo
inteiros que incluem estado complexo. ProtectedSessionStore serializa e desserializa
JSautomaticamente os dados ON para armazenar objetos de estado complexos.

No exemplo de código anterior, os currentCount dados são armazenados como


sessionStorage['count'] no navegador do usuário. Os dados não são armazenados em
texto sem formatação, mas sim protegidos usando ASP.NET Core Proteção de Dados. Os
dados criptografados podem ser inspecionados se sessionStorage['count'] forem
avaliados no console do desenvolvedor do navegador.

Para recuperar os currentCount dados se o usuário retornar ao Counter componente


posteriormente, incluindo se o usuário estiver em um novo circuito, use
ProtectedSessionStore.GetAsync :

C#

protected override async Task OnInitializedAsync()


{
var result = await ProtectedSessionStore.GetAsync<int>("count");
currentCount = result.Success ? result.Value : 0;
}

Se os parâmetros do componente incluirem o estado de navegação, chame


ProtectedSessionStore.GetAsync e atribua um resultado não null - em
OnParametersSetAsync, não OnInitializedAsyncem . OnInitializedAsync é chamado
apenas uma vez quando o componente é instanciado pela primeira vez.
OnInitializedAsync não será chamado novamente mais tarde se o usuário navegar para
uma URL diferente enquanto permanecer na mesma página. Para saber mais, consulte
Ciclo de vida de renderização de Razor no ASP.NET Core.

2 Aviso

Os exemplos nesta seção só funcionarão se o servidor não tiver a pré-geração


habilitada. Com a pré-geração habilitada, um erro é gerado explicando que as
chamadas de interoperabilidade do JavaScript não podem ser emitidas porque o
componente está sendo pré-gerado.
Desabilite a pré-geração ou adicione um código adicional para trabalhar com a
pré-geração. Para saber mais sobre como escrever código que funciona com a pré-
geração, consulte a seção Manipular pré-geração .

Manipular o estado de carregamento


Como o armazenamento do navegador é acessado de forma assíncrona em uma
conexão de rede, há sempre um período de tempo antes que os dados sejam
carregados e disponíveis para um componente. Para obter os melhores resultados,
renderize uma mensagem de estado de carregamento enquanto o carregamento estiver
em andamento em vez de exibir dados em branco ou padrão.

Uma abordagem é acompanhar se os dados são null , o que significa que os dados
ainda estão sendo carregados. No componente padrão Counter , a contagem é mantida
em um int . Torne currentCount anulável adicionando um ponto de interrogação ( ? ) ao
tipo ( int ):

C#

private int? currentCount;

Em vez de exibir incondicionalmente a contagem e Increment o botão, exiba esses


elementos somente se os dados forem carregados verificando HasValue:

razor

@if (currentCount.HasValue)
{
<p>Current count: <strong>@currentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>
}
else
{
<p>Loading...</p>
}

Manipular a pré-geração
Durante a pré-geração:

Uma conexão interativa com o navegador do usuário não existe.


O navegador ainda não tem uma página na qual pode executar código JavaScript.
localStorage ou sessionStorage não estão disponíveis durante a pré-geração. Se o

componente tentar interagir com o armazenamento, um erro será gerado explicando


que as chamadas de interoperabilidade do JavaScript não podem ser emitidas porque o
componente está sendo pré-gerado.

Uma maneira de resolver o erro é desabilitar a pré-geração. Essa geralmente é a melhor


opção se o aplicativo faz uso intenso do armazenamento baseado em navegador. A pré-
geração adiciona complexidade e não beneficia o aplicativo porque o aplicativo não
pode pré-gerar nenhum conteúdo útil até localStorage ou sessionStorage estar
disponível.

Para desabilitar a pré-geração, abra o Pages/_Host.cshtml arquivo e altere o render-


mode atributo do Auxiliar de Marca de Componente para :Server

CSHTML

<component type="typeof(App)" render-mode="Server" />

A pré-geração de <head> conteúdo está desabilitada no <head> conteúdo:

CSHTML

<component type="typeof(HeadOutlet)" render-mode="Server" />

A pré-geração pode ser útil para outras páginas que não usam localStorage ou
sessionStorage . Para manter a pré-geração, adie a operação de carregamento até que o
navegador esteja conectado ao circuito. Veja a seguir um exemplo para armazenar um
valor de contador:

razor

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
<p>Current count: <strong>@currentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>
}
else
{
<p>Loading...</p>
}

@code {
private int currentCount;
private bool isConnected;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
isConnected = true;
await LoadStateAsync();
StateHasChanged();
}
}

private async Task LoadStateAsync()


{
var result = await ProtectedLocalStore.GetAsync<int>("count");
currentCount = result.Success ? result.Value : 0;
}

private async Task IncrementCount()


{
currentCount++;
await ProtectedLocalStore.SetAsync("count", currentCount);
}
}

Fatorar a preservação do estado para um local comum


Se muitos componentes dependerem do armazenamento baseado em navegador, a
implementação do código do provedor de estado muitas vezes criará duplicação de
código. Uma opção para evitar a duplicação de código é criar um componente pai do
provedor de estado que encapsula a lógica do provedor de estado. Os componentes
filho podem trabalhar com dados persistentes sem levar em conta o mecanismo de
persistência de estado.

No exemplo a seguir de um CounterStateProvider componente, os dados do contador


são persistidos em sessionStorage :

razor

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
<CascadingValue Value="@this">
@ChildContent
</CascadingValue>
}
else
{
<p>Loading...</p>
}

@code {
private bool isLoaded;

[Parameter]
public RenderFragment? ChildContent { get; set; }

public int CurrentCount { get; set; }

protected override async Task OnInitializedAsync()


{
var result = await ProtectedSessionStore.GetAsync<int>("count");
CurrentCount = result.Success ? result.Value : 0;
isLoaded = true;
}

public async Task SaveChangesAsync()


{
await ProtectedSessionStore.SetAsync("count", CurrentCount);
}
}

7 Observação

Para obter mais informações sobre RenderFragment, consulte ASP.NET Core Razor
componentes.

O CounterStateProvider componente manipula a fase de carregamento não


renderizando seu conteúdo filho até que o carregamento de estado seja concluído.

Para usar o CounterStateProvider componente, encapsule uma instância do


componente em torno de qualquer outro componente que exija acesso ao estado do
contador. Para tornar o estado acessível a todos os componentes em um aplicativo,
encapsule o CounterStateProvider componente Router no App componente
( App.razor ):

razor

<CounterStateProvider>
<Router AppAssembly="@typeof(App).Assembly">
...
</Router>
</CounterStateProvider>
Os componentes encapsulados recebem e podem modificar o estado do contador
persistente. O seguinte Counter componente implementa o padrão:

razor

@page "/counter"

<p>Current count: <strong>@CounterStateProvider?.CurrentCount</strong></p>


<button @onclick="IncrementCount">Increment</button>

@code {
[CascadingParameter]
private CounterStateProvider? CounterStateProvider { get; set; }

private async Task IncrementCount()


{
if (CounterStateProvider is not null)
{
CounterStateProvider.CurrentCount++;
await CounterStateProvider.SaveChangesAsync();
}
}
}

O componente anterior não é necessário para interagir com , nem lida com
ProtectedBrowserStorage uma fase de "carregamento".

Para lidar com a pré-geração, conforme descrito anteriormente, CounterStateProvider


pode ser alterado para que todos os componentes que consomem os dados do
contador funcionem automaticamente com a pré-geração. Para obter mais informações,
consulte a seção Manipular pré-geração .

Em geral, o padrão de componente pai do provedor de estado é recomendado:

Para consumir o estado em muitos componentes.


Se houver apenas um objeto de estado de nível superior para persistir.

Para persistir muitos objetos de estado diferentes e consumir diferentes subconjuntos


de objetos em locais diferentes, é melhor evitar manter o estado globalmente.

Serviço de contêiner de estado na memória


Componentes aninhados normalmente associam dados usando associação encadeada,
conforme descrito em ASP.NET Core Blazor associação de dados. Componentes
aninhados e não conectados podem compartilhar o acesso aos dados usando um
contêiner de estado na memória registrado. Uma classe de contêiner de estado
personalizado pode usar um atribuível Action para notificar componentes em diferentes
partes do aplicativo de alterações de estado. No exemplo a seguir:

Um par de componentes usa um contêiner de estado para rastrear uma


propriedade.
Um componente no exemplo a seguir está aninhado no outro componente, mas o
aninhamento não é necessário para que essa abordagem funcione.

) Importante

O exemplo nesta seção demonstra como criar um serviço de contêiner de estado


na memória, registrar o serviço e usar o serviço em componentes. O exemplo não
persiste dados sem desenvolvimento adicional. Para o armazenamento persistente
de dados, o contêiner de estado deve adotar um mecanismo de armazenamento
subjacente que sobreviva quando a memória do navegador é desmarcada. Isso
pode ser feito com ou com localStorage / sessionStorage alguma outra tecnologia.

StateContainer.cs :

C#

public class StateContainer


{
private string? savedString;

public string Property


{
get => savedString ?? string.Empty;
set
{
savedString = value;
NotifyStateChanged();
}
}

public event Action? OnChange;

private void NotifyStateChanged() => OnChange?.Invoke();


}

In Program.cs (Blazor WebAssembly):

C#

builder.Services.AddSingleton<StateContainer>();
Em Program.cs (Blazor Server) no ASP.NET Core 6.0 ou posterior:

C#

builder.Services.AddScoped<StateContainer>();

Em Startup.ConfigureServices (Blazor Server) em versões de ASP.NET Core anteriores à


6.0:

C#

services.AddScoped<StateContainer>();

Shared/Nested.razor :

razor

@implements IDisposable
@inject StateContainer StateContainer

<h2>Nested component</h2>

<p>Nested component Property: <b>@StateContainer.Property</b></p>

<p>
<button @onclick="ChangePropertyValue">
Change the Property from the Nested component
</button>
</p>

@code {
protected override void OnInitialized()
{
StateContainer.OnChange += StateHasChanged;
}

private void ChangePropertyValue()


{
StateContainer.Property =
$"New value set in the Nested component: {DateTime.Now}";
}

public void Dispose()


{
StateContainer.OnChange -= StateHasChanged;
}
}

Pages/StateContainerExample.razor :
razor

@page "/state-container-example"
@implements IDisposable
@inject StateContainer StateContainer

<h1>State Container Example component</h1>

<p>State Container component Property: <b>@StateContainer.Property</b></p>

<p>
<button @onclick="ChangePropertyValue">
Change the Property from the State Container Example component
</button>
</p>

<Nested />

@code {
protected override void OnInitialized()
{
StateContainer.OnChange += StateHasChanged;
}

private void ChangePropertyValue()


{
StateContainer.Property = "New value set in the State " +
$"Container Example component: {DateTime.Now}";
}

public void Dispose()


{
StateContainer.OnChange -= StateHasChanged;
}
}

Os componentes anteriores implementam IDisposablee os OnChange delegados não são


assinados nos Dispose métodos, que são chamados pela estrutura quando os
componentes são descartados. Para saber mais, consulte Ciclo de vida de renderização
de Razor no ASP.NET Core.
Depurar ASP.NET Core Blazor
WebAssembly
Artigo • 04/01/2023 • 66 minutos para o fim da leitura

Este artigo descreve como depurar Blazor WebAssembly com ferramentas de


desenvolvedor do navegador e um IDE (ambiente de desenvolvimento integrado).

Blazor WebAssemblyos aplicativos podem ser depurados usando as ferramentas de


desenvolvedor do navegador em navegadores baseados em Chromium (Edge/Chrome).
Você também pode depurar seu aplicativo usando os seguintes IDEs:

Visual Studio
Visual Studio para Mac
Visual Studio Code

Os cenários disponíveis incluem:

Definir e remover pontos de interrupção.


Execute o aplicativo com suporte de depuração em IDEs.
Passo a passo pelo código.
Retome a execução de código com um atalho de teclado em IDEs.
Na janela Locais , observe os valores das variáveis locais.
Consulte a pilha de chamadas, incluindo cadeias de chamadas entre JavaScript e
.NET.

Por enquanto, você não pode:

Interromper em exceções sem tratamento.


Atingir pontos de interrupção durante a inicialização do aplicativo antes que o
proxy de depuração esteja em execução. Isso inclui pontos de interrupção no
Program.cs e pontos de interrupção nos OnInitialized{Async} métodos de ciclo de
vida dos componentes que são carregados pela primeira página solicitada do
aplicativo.
Depurar em cenários não locais (por exemplo, WSL (Subsistema do Windows para
Linux) ou Codespaces do Visual Studio).
Recompile automaticamente o aplicativo de back-end Server de uma solução
hospedada Blazor WebAssembly durante a depuração, por exemplo, executando o
aplicativo com dotnet watch run.

Pré-requisitos
A depuração requer um dos seguintes navegadores:

Google Chrome (versão 70 ou posterior) (padrão)


Microsoft Edge (versão 80 ou posterior)

Verifique se os firewalls ou proxies não bloqueiam a comunicação com o proxy de


depuração ( NodeJS processo). Para obter mais informações, consulte a seção
Configuração do firewall .

Visual Studio Code usuários exigem as seguintes extensões:

C# para extensão de Visual Studio Code


Blazor Extensão de depuração wasm (ao usar o C# para Visual Studio Code
Extensão 1.23.9 ou posterior)

Depois de abrir um projeto no VS Code, você pode receber uma notificação de que a
configuração adicional é necessária para habilitar a depuração. Se solicitado, instale as
extensões necessárias do Visual Studio Marketplace. Para inspecionar as extensões
instaladas, abra Exibir>Extensões na barra de menus ou selecione o ícone Extensões na
barra lateral Atividade .

Visual Studio para Mac requer a versão 8.8 (build 1532) ou posterior:

Instale a versão mais recente do Visual Studio para Mac selecionando o botão Baixar
Visual Studio para Mac na Microsoft: Visual Studio para Mac .

7 Observação

No momento, não há suporte para o Apple Safari no macOS.

Pacotes
Blazor WebAssembly autônomo:

Microsoft.AspNetCore.Components.WebAssembly.DevServer : servidor de
desenvolvimento para uso ao criar Blazor aplicativos. Chama
WebAssemblyNetDebugProxyAppBuilderExtensions.UseWebAssemblyDebugging
internamente para adicionar middleware para depuração de Blazor WebAssembly
aplicativos dentro Chromium ferramentas de desenvolvedor.

Blazor WebAssembly hospedado:


Client project: Microsoft.AspNetCore.Components.WebAssembly.DevServer :
servidor de desenvolvimento para uso ao criar Blazor aplicativos. Chama
WebAssemblyNetDebugProxyAppBuilderExtensions.UseWebAssemblyDebugging
internamente para adicionar middleware para depuração de Blazor WebAssembly
aplicativos dentro Chromium ferramentas de desenvolvedor.
Server project: Microsoft.AspNetCore.Components.WebAssembly.Server : faz
referência a um pacote interno
(Microsoft.NETCore.BrowserDebugHost.Transport ) para assemblies que
compartilham o host de depuração do navegador.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Depurar um aplicativo autônomo Blazor


WebAssembly
Para habilitar a depuração para um aplicativo existente Blazor WebAssembly , atualize o
launchSettings.json arquivo no projeto de inicialização para incluir a seguinte
inspectUri propriedade em cada perfil de inicialização:

JSON

"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-
proxy?browser={browserInspectUri}"

Depois de atualizado, o launchSettings.json arquivo deve ser semelhante ao exemplo a


seguir:

JSON

{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50454",
"sslPort": 44399
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:
{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"BlazorApp1.Server": {
"commandName": "Project",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:
{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

A inspectUri propriedade :

Permite que o IDE detecte que o aplicativo é um Blazor WebAssembly aplicativo.


Instrui a infraestrutura de depuração de script a se conectar ao navegador por
meio Blazordo proxy de depuração do .

Os valores de espaço reservado para o protocolo WebSocket ( wsProtocol ), host


( url.hostname ), porta ( url.port ) e URI do inspetor no navegador iniciado
( browserInspectUri ) são fornecidos pela estrutura.

Visual Studio

Para depurar um Blazor WebAssembly aplicativo no Visual Studio:

1. Crie uma nova solução hospedadaBlazor WebAssembly.

2. Com o Server projeto selecionado em Gerenciador de Soluções, pressione


F5 para executar o aplicativo no depurador.

7 Observação

Ao depurar com um navegador baseado em Chromium, como o Google


Chrome ou o Microsoft Edge, uma nova janela do navegador pode ser
aberta com um perfil separado para a sessão de depuração em vez de
abrir uma guia em uma janela do navegador existente com o perfil do
usuário. Se a depuração com o perfil do usuário for um requisito, adote
uma das seguintes abordagens:

Feche todas as instâncias abertas do navegador antes de pressionar


F5 para iniciar a depuração.
Configure o Visual Studio para iniciar o navegador com o perfil do
usuário. Para obter mais informações sobre essa abordagem,
consulte Blazor Depuração de WASM no VS inicia o Edge com um
diretório de dados de usuário separado (dotnet/aspnetcore
#20915) .

7 Observação

Não há suporte para Iniciar sem Depuração [ Ctrl + F5 (Windows) ou ⌘

+ F5 (macOS)]. Quando o aplicativo é executado na configuração de


Depuração, a sobrecarga de depuração sempre resulta em uma pequena
redução de desempenho.

3. Client No aplicativo, defina um ponto de interrupção na currentCount++;


linha em Pages/Counter.razor .

4. No navegador, navegue até a Counter página e selecione o botão Clique em


mim para acessar o ponto de interrupção.

5. No Visual Studio, inspecione o valor do currentCount campo na janela Locais .

6. Pressione F5 para continuar a execução.

Ao depurar um Blazor WebAssembly aplicativo, você também pode depurar o


código do servidor:

1. Defina um ponto de interrupção na Pages/FetchData.razor página em


OnInitializedAsync.
2. Defina um ponto de interrupção no WeatherForecastController no método de
Get ação.
3. Navegue até a Fetch Data página para atingir o primeiro ponto de
interrupção no FetchData componente pouco antes de emitir uma solicitação
HTTP para o servidor.
4. Pressione F5 para continuar a execução e, em seguida, pressione o ponto de
interrupção no servidor no WeatherForecastController .
5. Pressione F5 novamente para permitir que a execução continue e veja a
tabela de previsão do tempo renderizada no navegador.

7 Observação

Os pontos de interrupção não são atingidos durante a inicialização do


aplicativo antes da execução do proxy de depuração. Isso inclui pontos de
interrupção no Program.cs e pontos de interrupção nos OnInitialized{Async}
métodos de ciclo de vida dos componentes que são carregados pela primeira
página solicitada do aplicativo.

Se o aplicativo estiver hospedado em um caminho base de aplicativo diferente de


/ , atualize as seguintes propriedades em Properties/launchSettings.json para
refletir o caminho base do aplicativo:

applicationUrl :

JSON

"iisSettings": {
...
"iisExpress": {
"applicationUrl": "http://localhost:{INSECURE PORT}/{APP BASE
PATH}/",
"sslPort": {SECURE PORT}
}
},

inspectUri de cada perfil:

JSON

"profiles": {
...
"{PROFILE 1, 2, ... N}": {
...
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/{APP
BASE PATH}/_framework/debug/ws-proxy?browser={browserInspectUri}",
...
}
}

Os espaços reservados nas configurações anteriores:


{INSECURE PORT} : a porta insegura. Um valor aleatório é fornecido por padrão,

mas uma porta personalizada é permitida.


{APP BASE PATH} : o caminho base do aplicativo.

{SECURE PORT} : a porta segura. Um valor aleatório é fornecido por padrão,


mas uma porta personalizada é permitida.
{PROFILE 1, 2, ... N} : inicia perfis de configurações. Normalmente, um

aplicativo especifica mais de um perfil por padrão (por exemplo, um perfil


para IIS Express e um perfil de projeto, que é usado pelo Kestrel servidor).

Nos exemplos a seguir, o aplicativo é hospedado em /OAT com um caminho base


do aplicativo configurado em wwwroot/index.html como <base href="/OAT/"> :

JSON

"applicationUrl": "http://localhost:{INSECURE PORT}/OAT/",

JSON

"inspectUri": "{wsProtocol}://{url.hostname}:
{url.port}/OAT/_framework/debug/ws-proxy?browser={browserInspectUri}",

Para obter informações sobre como usar um caminho base de aplicativo


personalizado para Blazor WebAssembly aplicativos, consulte Hospedar e implantar
ASP.NET Core Blazor.

Depurar no navegador
As diretrizes nesta seção se aplicam ao Google Chrome e ao Microsoft Edge em execução
no Windows.

1. Execute um build de Depuração do aplicativo no ambiente de desenvolvimento.

2. Inicie um navegador e navegue até a URL do aplicativo.

3. No navegador, tente iniciar a depuração remota pressionando Shift + Alt + d .

O navegador deve estar em execução com a depuração remota habilitada, o que


não é o padrão. Se a depuração remota estiver desabilitada, uma página de erro
não é possível localizar a guia do navegador depurável será renderizada com
instruções para iniciar o navegador com a porta de depuração aberta. Siga as
instruções do navegador, que abre uma nova janela do navegador. Feche a janela
anterior do navegador.
1. Depois que o navegador estiver em execução com a depuração remota habilitada,
o atalho de teclado de depuração na etapa anterior abrirá uma nova guia do
depurador.

2. Após um momento, a guia Fontes mostra uma lista dos assemblies .NET do
aplicativo dentro do file:// nó.

3. Em código de componente ( .razor arquivos) e arquivos de código C#( .cs ), os


pontos de interrupção definidos são atingidos quando o código é executado.
Depois que um ponto de interrupção é atingido, uma etapa única ( F10 ) por meio
do código ou da execução do código de retomada ( F8 ) normalmente.

Blazor fornece um proxy de depuração que implementa o Chrome DevTools Protocol


e aumenta o protocolo com . Informações específicas do NET. Quando o atalho de
teclado de depuração é pressionado, Blazor aponta o Chrome DevTools para o proxy. O
proxy se conecta à janela do navegador que você está buscando depurar (daí a
necessidade de habilitar a depuração remota).

Mapas de origem do navegador


Os mapas de origem do navegador permitem que o navegador mapeie arquivos
compilados de volta para seus arquivos de origem originais e são comumente usados
para depuração do lado do cliente. No entanto, Blazor atualmente não mapeia C#
diretamente para JavaScript/WASM. Em vez disso, Blazor a interpretação de IL no
navegador, portanto, os mapas de origem não são relevantes.

Configuração do firewall
Se um firewall bloquear a comunicação com o proxy de depuração, crie uma regra de
exceção de firewall que permita a comunicação entre o navegador e o NodeJS processo.

2 Aviso

A modificação de uma configuração de firewall deve ser feita com cuidado para
evitar a criação de vulnerabilidades de segurança. Aplique cuidadosamente as
diretrizes de segurança, siga as melhores práticas de segurança e respeite os avisos
emitidos pelo fabricante do firewall.

Permitindo a comunicação aberta com o NodeJS processo:


Abre o servidor node para qualquer conexão, dependendo dos recursos e da
configuração do firewall.
Pode ser arriscado dependendo da rede.
É recomendado apenas em computadores desenvolvedores.

Se possível, permita apenas a comunicação aberta com o NodeJS processo em


redes confiáveis ou privadas.

Para obter diretrizes de configuração do Firewall do Windows , consulte Criar um


programa de entrada ou uma regra de serviço. Para obter mais informações, consulte
Windows Defender Firewall com Segurança Avançada e artigos relacionados no
conjunto de documentação do Firewall do Windows.

Solucionar problemas
Se você estiver com erros, as dicas a seguir podem ajudar:

Na guia Depurador , abra as ferramentas de desenvolvedor em seu navegador. No


console, execute localStorage.clear() para remover quaisquer pontos de
interrupção.
Confirme se você instalou e confiou no certificado de desenvolvimento HTTPS
ASP.NET Core. Para obter mais informações, consulte Impor HTTPS em ASP.NET
Core.
O Visual Studio requer a opção Habilitar depuração de JavaScript para ASP.NET
(Chrome, Edge e IE) em Ferramentas>Opções>Depuração>Geral. Essa é a
configuração padrão para o Visual Studio. Se a depuração não estiver funcionando,
confirme se a opção está selecionada.
Se o ambiente usar um proxy HTTP, verifique se ele localhost está incluído nas
configurações de bypass de proxy. Isso pode ser feito definindo a NO_PROXY
variável de ambiente em:
O launchSettings.json arquivo do projeto.
No nível de variáveis de ambiente do usuário ou do sistema para que ele se
aplique a todos os aplicativos. Ao usar uma variável de ambiente, reinicie o
Visual Studio para que a alteração entre em vigor.
Verifique se firewalls ou proxies não bloqueiam a comunicação com o proxy de
depuração ( NodeJS processo). Para obter mais informações, consulte a seção
Configuração do firewall .
Pontos de interrupção em OnInitialized{Async} não
atingidos
O Blazor proxy de depuração da estrutura leva pouco tempo para ser iniciado, portanto,
os pontos de interrupção nos métodos deOnInitialized{Async} ciclo de vida podem não
ser atingidos. Recomendamos adicionar um atraso no início do corpo do método para
dar ao proxy de depuração algum tempo para iniciar antes que o ponto de interrupção
seja atingido. Você pode incluir o atraso com base em uma if diretiva do compilador
para garantir que o atraso não esteja presente para uma compilação de versão do
aplicativo.

OnInitialized:

C#

protected override void OnInitialized()


{
#if DEBUG
Thread.Sleep(10000);
#endif

...
}

OnInitializedAsync:

C#

protected override async Task OnInitializedAsync()


{
#if DEBUG
await Task.Delay(10000);
#endif

...
}

Tempo limite do Visual Studio (Windows)


Se o Visual Studio gerar uma exceção de que o adaptador de depuração falhou ao
iniciar mencionando que o tempo limite foi atingido, você poderá ajustar o tempo limite
com uma configuração do Registro:

Console
VsRegEdit.exe set "<VSInstallFolder>" HKCU JSDebugger\Options\Debugging
"BlazorTimeoutInMilliseconds" dword {TIMEOUT}

O {TIMEOUT} espaço reservado no comando anterior está em milissegundos. Por


exemplo, um minuto é atribuído como 60000 .
Assemblies de carga lentos no ASP.NET
Core Blazor WebAssembly
Artigo • 08/11/2022 • 20 minutos para o fim da leitura

Blazor WebAssembly O desempenho de inicialização do aplicativo pode ser aprimorado


aguardando para carregar assemblies de aplicativo até que os assemblies sejam
necessários, o que é chamado de carregamento lento.

As seções iniciais deste artigo abordam a configuração do aplicativo. Para obter uma
demonstração de trabalho, consulte a seção De exemplo Completa no final deste artigo.

Este artigo só se aplica a Blazor WebAssembly aplicativos. O carregamento lento do


assembly não beneficia os Blazor Server aplicativos porque Blazor Server os assemblies
de aplicativo não são baixados para o cliente.

Configuração de arquivo do projeto


Marque assemblies para carregamento lento no arquivo de projeto do aplicativo
( .csproj ) usando o BlazorWebAssemblyLazyLoad item. Use o nome do assembly com a
.dll extensão. A Blazor estrutura impede que o assembly seja carregado na

inicialização do aplicativo.

XML

<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="{ASSEMBLY NAME}.dll" />
</ItemGroup>

O {ASSEMBLY NAME} espaço reservado é o nome do assembly. A .dll extensão de


arquivo é necessária.

Inclua um BlazorWebAssemblyLazyLoad item para cada assembly. Se um assembly tiver


dependências, inclua uma BlazorWebAssemblyLazyLoad entrada para cada dependência.

Router configuração do componente


A Blazor estrutura registra automaticamente um serviço singleton para assemblies de
carregamento lentos em aplicativos do lado Blazor WebAssembly do cliente†,
LazyAssemblyLoader. O método LazyAssemblyLoader.LoadAssembliesAsync:
JS Usa interoperabilidade para buscar assemblies por meio de uma chamada de
rede.
Carrega assemblies no runtime em execução no WebAssembly no navegador.

†Guidance parasoluçõeshospedadasBlazor WebAssembly é abordada nos assemblies de


carga Lazy em uma seção de solução hospedadaBlazor WebAssembly.

BlazorO componente 's Router designa os assemblies que pesquisam componentes


roteáveis e também é responsável por renderizar o componente para a rota em que
Blazor o usuário navega. O Router método do OnNavigateAsync componente é usado
em conjunto com o carregamento lento para carregar os assemblies corretos para
pontos de extremidade que um usuário solicita.

A lógica é implementada dentro OnNavigateAsync para determinar os assemblies a


serem carregados com LazyAssemblyLoader. As opções de como estruturar a lógica
incluem:

Verificações condicionais dentro do OnNavigateAsync método.


Uma tabela de pesquisa que mapeia rotas para nomes de assembly, injetadas no
componente ou implementadas dentro do @code bloco.

No exemplo a seguir:

O namespace para Microsoft.AspNetCore.Components.WebAssembly.Services é


especificado.
O LazyAssemblyLoader serviço é injetado ( AssemblyLoader ).
O {PATH} espaço reservado é o caminho onde a lista de assemblies deve ser
carregada. O exemplo usa uma verificação condicional para um único caminho que
carrega um único conjunto de assemblies.
O {LIST OF ASSEMBLIES} espaço reservado é a lista separada por vírgulas de
cadeias de caracteres de nome de arquivo de assembly, incluindo suas .dll
extensões (por exemplo, "Assembly1.dll", "Assembly2.dll" ).

App.razor :

razor

@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject LazyAssemblyLoader AssemblyLoader
@inject ILogger<App> Logger

<Router AppAssembly="@typeof(App).Assembly"
OnNavigateAsync="@OnNavigateAsync">
...
</Router>

@code {
private async Task OnNavigateAsync(NavigationContext args)
{
try
{
if (args.Path == "{PATH}")
{
var assemblies = await
AssemblyLoader.LoadAssembliesAsync(
new[] { {LIST OF ASSEMBLIES} });
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}

7 Observação

O exemplo anterior não mostra o conteúdo da Router marcação do Razor


componente ( ... ). Para obter uma demonstração com código completo, consulte
a seção De exemplo completa deste artigo.

Assemblies que incluem componentes


roteáveis
Quando a lista de assemblies inclui componentes roteáveis, a lista de assembly de um
determinado caminho é passada para a Router coleção do AdditionalAssemblies
componente.

No exemplo a seguir:

A Lista<Assembly> em lazyLoadedAssemblies passa a lista de assembly para


AdditionalAssemblies. A estrutura pesquisa as rotas dos assemblies e atualiza a
coleção de rotas se novas rotas forem encontradas. Para acessar o Assembly tipo,
o namespace para System.Reflection o qual está incluído na parte superior do
App.razor arquivo.
O {PATH} espaço reservado é o caminho onde a lista de assemblies deve ser
carregada. O exemplo usa uma verificação condicional para um único caminho que
carrega um único conjunto de assemblies.
O {LIST OF ASSEMBLIES} espaço reservado é a lista separada por vírgulas de
cadeias de caracteres de nome de arquivo de assembly, incluindo suas .dll
extensões (por exemplo, "Assembly1.dll", "Assembly2.dll" ).

App.razor :

razor

@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject LazyAssemblyLoader AssemblyLoader
@inject ILogger<App> Logger

<Router AppAssembly="@typeof(App).Assembly"
AdditionalAssemblies="@lazyLoadedAssemblies"
OnNavigateAsync="@OnNavigateAsync">
...
</Router>

@code {
private List<Assembly> lazyLoadedAssemblies = new();

private async Task OnNavigateAsync(NavigationContext args)


{
try
{
if (args.Path == "{PATH}")
{
var assemblies = await
AssemblyLoader.LoadAssembliesAsync(
new[] { {LIST OF ASSEMBLIES} });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}

7 Observação

O exemplo anterior não mostra o conteúdo da Router marcação do Razor


componente ( ... ). Para obter uma demonstração com código completo, consulte
a seção De exemplo completa deste artigo.
Para obter mais informações, consulte ASP.NET Core Blazor roteamento e navegação.

Interação do usuário com <Navigating>


conteúdo
Ao carregar assemblies, o que pode levar vários segundos, o Router componente pode
indicar ao usuário que uma transição de página está ocorrendo com a propriedade do
Navigating roteador.

Para obter mais informações, consulte ASP.NET Core Blazor roteamento e navegação.

Manipular cancelamentos em OnNavigateAsync


O NavigationContext objeto passado para o OnNavigateAsync retorno de chamada
contém um CancellationToken que é definido quando ocorre um novo evento de
navegação. O OnNavigateAsync retorno de chamada deve ser gerado quando o token
de cancelamento está definido para evitar continuar a executar o OnNavigateAsync
retorno de chamada em uma navegação desatualizada.

Para obter mais informações, consulte ASP.NET Core Blazor roteamento e navegação.

OnNavigateAsync eventos e arquivos de


assembly renomeados
O carregador de recursos depende dos nomes de assembly definidos no
blazor.boot.json arquivo. Se os assemblies forem renomeados, os nomes de assembly

usados em um OnNavigateAsync retorno de chamada e os nomes de assembly no


blazor.boot.json arquivo estarão fora de sincronia.

Para corrigir isso:

Verifique se o aplicativo está em execução no ambiente ao Production determinar


quais nomes de assembly usar.
Armazene os nomes de assembly renomeados em um arquivo separado e leia
desse arquivo para determinar qual nome de assembly usar com o serviço e
OnNavigateAsync o LazyAssemblyLoader retorno de chamada.
Assemblies de carga lentos em uma solução
hospedada Blazor WebAssembly
A implementação de carregamento lento da estrutura dá suporte ao carregamento
lento com pré-geração em uma solução hospedadaBlazor WebAssembly. Durante a pré-
geração, todos os assemblies, incluindo aqueles marcados para carregamento lento, são
considerados carregados. Registre manualmente o LazyAssemblyLoader serviço no
Server projeto.

Na parte superior do Program.cs arquivo do Server projeto, adicione o namespace para


Microsoft.AspNetCore.Components.WebAssembly.Services:

C#

using Microsoft.AspNetCore.Components.WebAssembly.Services;

Server No Program.cs projeto, registre o serviço:

C#

builder.Services.AddScoped<LazyAssemblyLoader>();

Exemplo completo
A demonstração nesta seção:

Cria um assembly de controles de robô ( GrantImaharaRobotControls.dll ) como


uma Razor RCL (biblioteca de classes) que inclui um Robot componente
( Robot.razor com um modelo de rota de /robot ).
Carrega preguiçosamente o assembly da RCL para renderizar seu Robot
componente quando a /robot URL é solicitada pelo usuário.

1. Crie um novo projeto de biblioteca de classes ASP.NET Core:

Visual Studio: criar uma solução>Crie uma biblioteca de classes de


projeto>Razor. Dê ao projeto o nome de GrantImaharaRobotControls .
CLI do Visual Studio Code/.NET: execute dotnet new razorclasslib -o
GrantImaharaRobotControls de um prompt de comando. A -o|--output opção

cria uma pasta para a solução e nomeia o projeto


GrantImaharaRobotControls .
2. O componente de exemplo apresentado posteriormente nesta seção usa um
Blazor formulário. No projeto RCL, adicione o
Microsoft.AspNetCore.Components.Forms pacote ao projeto.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET,


consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de
consumo de pacotes (documentação do NuGet). Confirme as versões
corretas de pacote em NuGet.org .

3. Crie uma HandGesture classe na RCL com um ThumbUp método que


hipoteticamente faz com que um robô execute um gesto de polegar para cima. O
método aceita um argumento para o eixo ou Right , Left como um enum. O
método retorna true com êxito.

HandGesture.cs :

C#

using Microsoft.Extensions.Logging;

namespace GrantImaharaRobotControls
{
public static class HandGesture
{
public static bool ThumbUp(Axis axis, ILogger logger)
{
logger.LogInformation("Thumb up gesture. Axis: {Axis}",
axis);

// Code to make robot perform gesture

return true;
}
}

public enum Axis { Left, Right }


}

4. Adicione o componente a seguir à raiz do projeto RCL. O componente permite que


o usuário envie uma solicitação de gesto de mão esquerda ou direita para cima.

Robot.razor :

razor
@page "/robot"
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Logging
@inject ILogger<Robot> Logger

<h1>Robot</h1>

<EditForm Model="@robotModel" OnValidSubmit="@HandleValidSubmit">


<InputRadioGroup @bind-Value="robotModel.AxisSelection">
@foreach (var entry in (Axis[])Enum
.GetValues(typeof(Axis)))
{
<InputRadio Value="@entry" />
<text>&nbsp;</text>@entry<br>
}
</InputRadioGroup>

<button type="submit">Submit</button>
</EditForm>

<p>
@message
</p>

@code {
private RobotModel robotModel = new() { AxisSelection = Axis.Left
};
private string? message;

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called");

var result = HandGesture.ThumbUp(robotModel.AxisSelection,


Logger);

message = $"ThumbUp returned {result} at {DateTime.Now}.";


}

public class RobotModel


{
public Axis AxisSelection { get; set; }
}
}

Crie um Blazor WebAssembly aplicativo para demonstrar o carregamento lento do


assembly da RCL:

1. Crie o Blazor WebAssembly aplicativo no Visual Studio, Visual Studio Code ou por
meio de um prompt de comando com a CLI do .NET. Dê ao projeto o nome de
LazyLoadTest .
2. Crie uma referência de projeto para a GrantImaharaRobotControls RCL:

Visual Studio: adicione o GrantImaharaRobotControls projeto RCL à solução


(Adicionar>Projeto Existente). Selecione Adicionar>Referência de Projeto
para adicionar uma referência de projeto para a GrantImaharaRobotControls
RCL.
CLI do Visual Studio Code/.NET: execute dotnet add reference {PATH} em um
shell de comando da pasta do projeto. O {PATH} espaço reservado é o
caminho para o projeto RCL.

Compile e execute o aplicativo. Para a página padrão que carrega o Index componente
( Pages/Index.razor ), a guia Rede da ferramenta de desenvolvedor indica que o
assembly GrantImaharaRobotControls.dll da RCL está carregado. O Index componente
não usa o assembly, portanto, carregar o assembly é ineficiente.
Configure o aplicativo para carregar o GrantImaharaRobotControls.dll assembly com
preguiça:

1. Especifique o assembly da RCL para carregamento lento no Blazor WebAssembly


arquivo de projeto do aplicativo ( .csproj ):

XML

<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="GrantImaharaRobotControls.dll" />
</ItemGroup>

2. Router O componente a seguir demonstra o carregamento do


GrantImaharaRobotControls.dll assembly quando o usuário navega para /robot .

Substitua o componente padrão App do aplicativo pelo componente a seguir App .

Durante as transições de página, uma mensagem com estilo é exibida para o


usuário com o <Navigating> elemento. Para obter mais informações, consulte a
seção Interação do usuário com <Navigating> o conteúdo .

O assembly é atribuído , AdditionalAssemblieso que resulta no roteador


pesquisando o assembly em busca de componentes roteáveis, no qual ele
encontra o Robot componente. A Robot rota do componente é adicionada à
coleção de rotas do aplicativo. Para obter mais informações, consulte o artigo
ASP.NET Core Blazor de roteamento e navegação e os Assemblies que incluem a
seção componentes roteáveis deste artigo.

App.razor :

razor

@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject LazyAssemblyLoader AssemblyLoader
@inject ILogger<App> Logger

<Router AppAssembly="@typeof(App).Assembly"
AdditionalAssemblies="@lazyLoadedAssemblies"
OnNavigateAsync="@OnNavigateAsync">
<Navigating>
<div style="padding:20px;background-color:blue;color:white">
<p>Loading the requested page&hellip;</p>
</div>
</Navigating>
<Found Context="routeData">
<RouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

@code {
private List<Assembly> lazyLoadedAssemblies = new();

private async Task OnNavigateAsync(NavigationContext args)


{
try
{
if (args.Path == "robot")
{
var assemblies = await
AssemblyLoader.LoadAssembliesAsync(
new[] { "GrantImaharaRobotControls.dll" });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}

Crie e execute o aplicativo novamente. Para a página padrão que carrega o Index
componente ( Pages/Index.razor ), a guia Rede da ferramenta de desenvolvedor indica
que o assembly da RCL ( GrantImaharaRobotControls.dll ) não é carregado para o Index
componente:

Se o Robot componente da RCL for solicitado, /robot o GrantImaharaRobotControls.dll


assembly será carregado e o Robot componente será renderizado:
Solucionar problemas
Se ocorrer uma renderização inesperada, como renderizar um componente de
uma navegação anterior, confirme se o código será gerado se o token de
cancelamento estiver definido.
Se os assemblies configurados para carregamento lento forem carregados
inesperadamente no início do aplicativo, verifique se o assembly está marcado
para carregamento lento no arquivo de projeto.

7 Observação
Existe um problema conhecido para carregar tipos de um assembly com
preguiçosamente carregado. Para obter mais informações, consulte Blazor
WebAssembly lazy loading assemblies not working when using @ref attribute in
the component (dotnet/aspnetcore #29342) .

Recursos adicionais
Manipular eventos de navegação assíncronos com OnNavigateAsync
Blazor ASP.NET Core práticas recomendadas de desempenho
Blazor WebAssembly ASP.NET Core
dependências nativas
Artigo • 28/11/2022 • 4 minutos para o fim da leitura

Blazor WebAssembly os aplicativos podem usar dependências nativas criadas para


serem executadas no WebAssembly. Você pode vincular dependências nativas
estaticamente ao runtime do .NET WebAssembly usando as ferramentas de build do
.NET WebAssembly, as mesmas ferramentas usadas para compilar um Blazor aplicativo
para o WebAssembly e para revincular o runtime para remover recursos não utilizados.

Este artigo só se aplica a Blazor WebAssembly.

Ferramentas de build do .NET WebAssembly


As ferramentas de build do .NET WebAssembly são baseadas no Emscripten , uma
cadeia de ferramentas do compilador para a plataforma Web. Para obter mais
informações sobre as ferramentas de build, incluindo a instalação, consulte Ferramentas
para ASP.NET CoreBlazor.

Adicione dependências nativas a um Blazor WebAssembly aplicativo adicionando


NativeFileReference itens no arquivo de projeto do aplicativo. Quando o projeto é
criado, cada um NativeFileReference é passado para o Emscripten pelas ferramentas de
build do .NET WebAssembly para que sejam compilados e vinculados ao runtime. Em
seguida, p/invoke no código nativo do código .NET do aplicativo.

Em geral, qualquer código nativo portátil pode ser usado como uma dependência nativa
com Blazor WebAssembly. Você pode adicionar dependências nativas ao código ou
código C/C++ compilados anteriormente usando o Emscripten:

Arquivos de objeto ( .o )
Arquivos de arquivo morto ( .a )
Bitcode ( .bc )
Módulos WebAssembly autônomos ( .wasm )

As dependências predefinidas normalmente devem ser criadas usando a mesma versão


do Emscripten usada para criar o runtime do .NET WebAssembly.

7 Observação
Para propriedades e destinos do MSBuild Mono/WebAssembly, consulte
WasmApp.targets (repositório GitHub dotnet/runtime) . A documentação oficial
para propriedades comuns do MSBuild é planejada por opções de configuração
do Documento blazor msbuild (dotnet/docs #27395) .

Usar código nativo


Adicione uma função C nativa simples a um Blazor WebAssembly aplicativo:

1. Criar um novo projeto do Blazor WebAssembly .

2. Adicione um Test.c arquivo ao projeto.

3. Adicione uma função C para fatores de computação.

Test.c :

int fact(int n)
{
if (n == 0) return 1;
return n * fact(n - 1);
}

4. Adicione um NativeFileReference for Test.c no arquivo de projeto do aplicativo:

XML

<ItemGroup>
<NativeFileReference Include="Test.c" />
</ItemGroup>

5. Em um Razor componente, adicione um DllImportAttribute para a fact função na


biblioteca gerada Test e chame o fact método do código .NET no componente.

Pages/NativeCTest.razor :

razor

@page "/native-c-test"
@using System.Runtime.InteropServices

<PageTitle>Native C</PageTitle>
<h1>Native C Test</h1>

<p>
@@fact(3) result: @fact(3)
</p>

@code {
[DllImport("Test")]
static extern int fact(int n);
}

Quando você cria o aplicativo com as ferramentas de build do .NET WebAssembly


instaladas, o código C nativo é compilado e vinculado ao runtime do .NET WebAssembly
( dotnet.wasm ). Depois que o aplicativo for criado, execute o aplicativo para ver o valor
fatorial renderizado.

Retornos de chamada do método gerenciado


do C++
Rotule os métodos gerenciados que são passados para o C++ com o
[UnmanagedCallersOnly] atributo.

O método marcado com o [UnmanagedCallersOnly] atributo deve ser static . Para


chamar um método de instância em um Razor componente, passe um GCHandle para a
instância para C++ e, em seguida, passe-o de volta para nativo. Como alternativa, use
algum outro método para identificar a instância do componente.

O método marcado com [DllImport] deve usar um ponteiro de função C# 9.0 em vez
de um tipo delegado para o argumento de retorno de chamada.

7 Observação

Para tipos de ponteiro de função C# em [DllImport] métodos, use IntPtr na


assinatura do método no lado gerenciado em vez de delegate *unmanaged<int,
void> . Para obter mais informações, consulte o retorno de chamada [WASM] do
código nativo para o .NET: não há suporte para analisar tipos de ponteiro de
função em assinaturas (dotnet/runtime #56145) .

Empacotar dependências nativas em um pacote


NuGet
Os pacotes NuGet podem conter dependências nativas para uso no WebAssembly.
Essas bibliotecas e suas funcionalidades nativas estão disponíveis para qualquer Blazor
WebAssembly aplicativo. Os arquivos para as dependências nativas devem ser criados
para WebAssembly e empacotados na pasta específica da browser-wasm arquitetura. As
dependências específicas do WebAssembly não são referenciadas automaticamente e
devem ser referenciadas manualmente como NativeFileReference s. Os autores do
pacote podem optar por adicionar as referências nativas incluindo um .props arquivo
no pacote com as referências.

Uso da biblioteca de exemplo skiaSharp


SkiaSharp é uma biblioteca de elementos gráficos 2D multiplataforma para .NET com
base na biblioteca de gráficos skia nativa com suporte para Blazor WebAssembly.

Para usar o SkiaSharp em um Blazor WebAssembly aplicativo:

1. Adicione uma referência de pacote ao SkiaSharp.Views.Blazor pacote em um


Blazor WebAssembly projeto. Use o processo do Visual Studio para adicionar
pacotes a um aplicativo (Gerenciar Pacotes NuGet com o pré-lançamento Incluir
selecionado) ou execute o dotnet add package comando em um shell de
comando:

CLI do .NET

dotnet add package –-prerelease SkiaSharp.Views.Blazor

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET,


consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de
consumo de pacotes (documentação do NuGet). Confirme as versões
corretas de pacote em NuGet.org .

2. Adicione um SKCanvasView componente ao aplicativo com o seguinte:

SkiaSharp e SkiaSharp.Views.Blazor namespaces.


Lógica a ser desenhada no componente Exibição de Tela skiaSharp
( SKCanvasView ).

Pages/NativeDependencyExample.razor :
razor

@page "/native-dependency-example"
@using SkiaSharp
@using SkiaSharp.Views.Blazor

<PageTitle>Native dependency</PageTitle>

<h1>Native dependency example with SkiaSharp</h1>

<SKCanvasView OnPaintSurface="@OnPaintSurface" />

@code {
private void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;

canvas.Clear(SKColors.White);

using var paint = new SKPaint


{
Color = SKColors.Black,
IsAntialias = true,
TextSize = 24
};

canvas.DrawText("SkiaSharp", 0, 24, paint);


}
}

3. Crie o aplicativo, o que pode levar vários minutos. Execute o aplicativo e navegue
até o NativeDependencyExample componente em /native-dependency-example .

Recursos adicionais
Ferramentas de build do .NET WebAssembly
Blazor práticas recomendadas de
desempenho do ASP.NET Core
Artigo • 28/11/2022 • 96 minutos para o fim da leitura

Blazor é otimizado para alto desempenho em cenários de interface do usuário de


aplicativo mais realistas. No entanto, o melhor desempenho depende dos
desenvolvedores adotarem os padrões e recursos corretos.

Otimizar a velocidade de renderização


Otimize a velocidade de renderização para minimizar a carga de trabalho de
renderização e melhorar a capacidade de resposta da interface do usuário, o que pode
produzir uma melhoria de dez vezes ou mais na velocidade de renderização da interface
do usuário.

Evitar a renderização desnecessária de subárvores de


componente
Você pode remover a maior parte do custo de renderização de um componente pai
ignorando a rerendering de subárvores de componente filho quando ocorre um evento.
Você só deve se preocupar em ignorar as subárvores de rerendering que são
particularmente caras para renderizar e estão causando o retardo da interface do
usuário.

Em runtime, os componentes existem em uma hierarquia. Um componente raiz tem


componentes filho. Por sua vez, os filhos da raiz têm seus próprios componentes filho e
assim por diante. Quando ocorre um evento, como um usuário selecionando um botão,
o processo a seguir determina quais componentes gerar novamente:

1. O evento é expedido para o componente que renderiza o manipulador do evento.


Depois de executar o manipulador de eventos, o componente é gerado
novamente.
2. Quando um componente é rerender, ele fornece uma nova cópia de valores de
parâmetro para cada um de seus componentes filho.
3. Depois que um novo conjunto de valores de parâmetro é recebido, cada
componente decide se deseja ser rerender. Por padrão, os componentes geram
novamente se os valores de parâmetro podem ter sido alterados, por exemplo, se
forem objetos mutáveis.
As duas últimas etapas da sequência anterior continuam recursivamente abaixo da
hierarquia de componentes. Em muitos casos, toda a subárvore é gerada novamente.
Eventos direcionados a componentes de alto nível podem causar uma nova geração
cara porque cada componente abaixo do componente de alto nível deve ser gerado
novamente.

Para evitar a recursão de renderização em uma subárvore específica, use uma das
seguintes abordagens:

Verifique se os parâmetros de componente filho são de tipos imutáveis primitivos,


como string , int , bool , DateTime e outros tipos semelhantes. A lógica interna
para detectar alterações ignora automaticamente a rerendering se os valores de
parâmetro imutáveis primitivos não foram alterados. Se você renderizar um
componente filho com <Customer CustomerId="@item.CustomerId" /> , em que
CustomerId é um int tipo, o Customer componente não será gerado novamente, a

menos que item.CustomerId seja alterado.


Substituir ShouldRender:
Para aceitar valores de parâmetro nãoprimitivos, como tipos de modelo
personalizado complexos, retornos de chamada de evento ou RenderFragment
valores.
Se estiver criando um componente somente de interface do usuário que não
seja alterado após a renderização inicial, independentemente das alterações de
valor do parâmetro.

O exemplo de ferramenta de pesquisa de voo de companhia aérea a seguir usa campos


privados para acompanhar as informações necessárias para detectar alterações. O
identificador de voo de entrada anterior ( prevInboundFlightId ) e o identificador de voo
de saída anterior ( prevOutboundFlightId ) acompanham as informações para a próxima
atualização potencial do componente. Se um dos identificadores de versão de pré-
lançamento for alterado quando os parâmetros do componente forem definidos em
OnParametersSet, o componente será gerado novamente porque está definido
true como shouldRender . Se shouldRender for avaliado como false depois de verificar
os identificadores de voo, uma nova geração cara será evitada:

razor

@code {
private int prevInboundFlightId = 0;
private int prevOutboundFlightId = 0;
private bool shouldRender;

[Parameter]
public FlightInfo? InboundFlight { get; set; }
[Parameter]
public FlightInfo? OutboundFlight { get; set; }

protected override void OnParametersSet()


{
shouldRender = InboundFlight?.FlightId != prevInboundFlightId
|| OutboundFlight?.FlightId != prevOutboundFlightId;

prevInboundFlightId = InboundFlight?.FlightId ?? 0;
prevOutboundFlightId = OutboundFlight?.FlightId ?? 0;
}

protected override bool ShouldRender() => shouldRender;


}

Um manipulador de eventos também pode definir shouldRender como true . Para a


maioria dos componentes, determinar a rerendering no nível de manipuladores de
eventos individuais geralmente não é necessário.

Para saber mais, consulte os recursos a seguir:

ciclo de Razor vida do componente ASP.NET Core


ShouldRender
Razor renderização do componente ASP.NET Core

Virtualização
Ao renderizar grandes quantidades de interface do usuário em um loop, por exemplo,
uma lista ou grade com milhares de entradas, a grande quantidade de operações de
renderização pode levar a um atraso na renderização da interface do usuário. Dado que
o usuário só pode ver um pequeno número de elementos de uma só vez sem rolar,
muitas vezes é um desperdício gastar tempo renderizando elementos que não estão
visíveis no momento.

Blazor fornece o Virtualize componente para criar os comportamentos de aparência e


rolagem de uma lista arbitrariamente grande, renderizando apenas os itens de lista que
estão dentro do visor de rolagem atual. Por exemplo, um componente pode renderizar
uma lista com 100.000 entradas, mas pagar apenas o custo de renderização de 20 itens
visíveis.

Para obter mais informações, consulte ASP.NET Core Razor virtualização de


componentes.

Criar componentes leves e otimizados


A maioria dos Razor componentes não exige esforços agressivos de otimização porque
a maioria dos componentes não se repete na interface do usuário e não gera
novamente em alta frequência. Por exemplo, componentes roteáveis com uma @page
diretiva e componentes usados para renderizar partes de alto nível da interface do
usuário, como caixas de diálogo ou formulários, provavelmente aparecem apenas um de
cada vez e apenas são gerados novamente em resposta a um gesto do usuário. Esses
componentes geralmente não criam uma carga de trabalho de renderização alta,
portanto, você pode usar livremente qualquer combinação de recursos de estrutura sem
muita preocupação com o desempenho de renderização.

No entanto, há cenários comuns em que os componentes são repetidos em escala e


geralmente resultam em baixo desempenho da interface do usuário:

Formulários aninhados grandes com centenas de elementos individuais, como


entradas ou rótulos.
Grades com centenas de linhas ou milhares de células.
Gráficos de dispersão com milhões de pontos de dados.

Se a modelagem de cada elemento, célula ou ponto de dados como uma instância de


componente separada, muitas vezes há tantos deles que seu desempenho de
renderização se torna crítico. Esta seção fornece conselhos sobre como tornar esses
componentes leves para que a interface do usuário permaneça rápida e responsiva.

Evitar milhares de instâncias de componente

Cada componente é uma ilha separada que pode renderizar independentemente de


seus pais e filhos. Ao escolher como dividir a interface do usuário em uma hierarquia de
componentes, você está assumindo o controle sobre a granularidade da renderização
da interface do usuário. Isso pode resultar em um desempenho bom ou ruim.

Ao dividir a interface do usuário em componentes separados, você pode ter partes


menores da rerender da interface do usuário quando ocorrem eventos. Em uma tabela
com muitas linhas que têm um botão em cada linha, você pode ter apenas essa única
linha rerender usando um componente filho em vez de toda a página ou tabela. No
entanto, cada componente requer memória adicional e sobrecarga de CPU para lidar
com seu estado independente e ciclo de vida de renderização.

Em um teste executado pelo ASP.NET Core engenheiros de unidade de produto, uma


sobrecarga de renderização de cerca de 0,06 ms por instância de componente foi vista
em um Blazor WebAssembly aplicativo. O aplicativo de teste renderiza um componente
simples que aceita três parâmetros. Internamente, a sobrecarga é em grande parte
devido à recuperação do estado por componente de dicionários e passagem e
recebimento de parâmetros. Por multiplicação, você pode ver que adicionar 2.000
instâncias de componente adicionais adicionaria 0,12 segundos ao tempo de
renderização e a interface do usuário começaria a ficar lenta para os usuários.

É possível tornar os componentes mais leves para que você possa ter mais deles. No
entanto, uma técnica mais poderosa geralmente é evitar ter tantos componentes para
renderizar. As seções a seguir descrevem duas abordagens que você pode adotar.

Componentes filho embutidos em seus pais

Considere a seguinte parte de um componente pai que renderiza componentes filho em


um loop:

razor

<div class="chat">
@foreach (var message in messages)
{
<ChatMessageDisplay Message="@message" />
}
</div>

Shared/ChatMessageDisplay.razor :

razor

<div class="chat-message">
<span class="author">@Message.Author</span>
<span class="text">@Message.Text</span>
</div>

@code {
[Parameter]
public ChatMessage? Message { get; set; }
}

O exemplo anterior terá um bom desempenho se milhares de mensagens não forem


mostradas de uma só vez. Para mostrar milhares de mensagens de uma só vez,
considere não considerar o componente separado ChatMessageDisplay . Em vez disso,
embuta o componente filho no pai. A abordagem a seguir evita a sobrecarga por
componente de renderizar tantos componentes filho ao custo de perder a capacidade
de gerar novamente a marcação de cada componente filho de forma independente:

razor
<div class="chat">
@foreach (var message in messages)
{
<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>
}
</div>

Definir reutilizável RenderFragments no código

Você pode estar fatorando componentes filho puramente como uma forma de reutilizar
a lógica de renderização. Se esse for o caso, você poderá criar uma lógica de
renderização reutilizável sem implementar componentes adicionais. No bloco de @code
qualquer componente, defina um RenderFragment. Renderize o fragmento de qualquer
local quantas vezes forem necessárias:

razor

@RenderWelcomeInfo

<p>Render the welcome content a second time:</p>

@RenderWelcomeInfo

@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!
</p>;
}

Para tornar RenderTreeBuilder o código reutilizável em vários componentes, declare e


staticRenderFragmentpublic :

razor

public static RenderFragment SayHello = @<h1>Hello!</h1>;

SayHello no exemplo anterior pode ser invocado de um componente não relacionado.

Essa técnica é útil para criar bibliotecas de snippets de marcação reutilizáveis que são
renderizados sem sobrecarga por componente.

RenderFragment delegados podem aceitar parâmetros. O seguinte componente passa a


mensagem ( message ) para o RenderFragment delegado:
razor

<div class="chat">
@foreach (var message in messages)
{
@ChatMessageDisplay(message)
}
</div>

@code {
private RenderFragment<ChatMessage> ChatMessageDisplay = message =>
@<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>;
}

A abordagem anterior reutiliza a lógica de renderização sem sobrecarga por


componente. No entanto, a abordagem não permite atualizar a subárvore da interface
do usuário de forma independente, nem tem a capacidade de ignorar a renderização da
subárvore da interface do usuário quando seu pai é renderizado porque não há nenhum
limite de componente. A atribuição a um RenderFragment delegado só tem suporte em
Razor arquivos de componente ( .razor ) e não há suporte para retornos de chamada de
evento .

Para um campo, método ou propriedade não estático que não pode ser referenciado
por um inicializador de campo, como TitleTemplate no exemplo a seguir, use uma
propriedade em vez de um campo para o RenderFragment:

C#

protected RenderFragment DisplayTitle =>


@<div>
@TitleTemplate
</div>;

Não receba muitos parâmetros

Se um componente se repetir com muita frequência, por exemplo, centenas ou milhares


de vezes, a sobrecarga de passar e receber cada parâmetro se acumulará.

É raro que muitos parâmetros restrinjam severamente o desempenho, mas pode ser um
fator. Para um TableCell componente que renderiza 1.000 vezes em uma grade, cada
parâmetro extra passado para o componente pode adicionar cerca de 15 ms ao custo
total de renderização. Se cada célula aceitasse 10 parâmetros, a passagem de
parâmetros levaria cerca de 150 ms por componente para um custo total de
renderização de 150.000 ms (150 segundos) e causaria um retardo de renderização da
interface do usuário.

Para reduzir a carga de parâmetros, agrupe vários parâmetros em uma classe


personalizada. Por exemplo, um componente de célula de tabela pode aceitar um
objeto comum. No exemplo a seguir, Data é diferente para cada célula, mas Options é
comum em todas as instâncias de célula:

razor

@typeparam TItem

...

@code {
[Parameter]
public TItem? Data { get; set; }

[Parameter]
public GridOptions? Options { get; set; }
}

No entanto, considere que pode ser uma melhoria não ter um componente de célula de
tabela, conforme mostrado no exemplo anterior e, em vez disso, embutido sua lógica
no componente pai.

7 Observação

Quando várias abordagens estão disponíveis para melhorar o desempenho, o


parâmetro de comparação das abordagens geralmente é necessário para
determinar qual abordagem produz os melhores resultados.

Para obter mais informações sobre parâmetros de tipo genérico ( @typeparam ), consulte
os seguintes recursos:

Razor Referência de sintaxe para ASP.NET Core


Razor componentes do ASP.NET Core
Componentes com modelo Blazor no ASP.NET Core

Verifique se os parâmetros em cascata estão fixos

O CascadingValue componente tem um parâmetro opcional IsFixed :


Se IsFixed for false (o padrão), cada destinatário do valor em cascata
configurará uma assinatura para receber notificações de alteração. Cada
[CascadingParameter] um é substancialmente mais caro do que um normal

[Parameter] devido ao acompanhamento da assinatura.


Se IsFixed for true (por exemplo, <CascadingValue Value="@someValue"
IsFixed="true"> ), os destinatários receberão o valor inicial, mas não configurarão

uma assinatura para receber atualizações. Cada [CascadingParameter] um é leve e


não mais caro do que um normal [Parameter] .

Definir IsFixed como true melhora o desempenho se houver um grande número de


outros componentes que recebem o valor em cascata. Sempre que possível, defina
IsFixed como true em valores em cascata. Você pode definir IsFixed true como

quando o valor fornecido não é alterado ao longo do tempo.

Quando um componente passa this como um valor em cascata, IsFixed também


pode ser definido true como :

razor

<CascadingValue Value="this" IsFixed="true">


<SomeOtherComponents>
</CascadingValue>

Para obter mais informações, consulte ASP.NET Core Blazor valores e parâmetros em
cascata.

Evitar a modelagem de atributo com CaptureUnmatchedValues

Os componentes podem optar por receber valores de parâmetro "incompatíveis"


usando o CaptureUnmatchedValues sinalizador :

razor

<div @attributes="OtherAttributes">...</div>

@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? OtherAttributes { get; set; }
}

Essa abordagem permite passar atributos adicionais arbitrários para o elemento . No


entanto, essa abordagem é cara porque o renderizador deve:
Corresponda todos os parâmetros fornecidos com o conjunto de parâmetros
conhecidos para criar um dicionário.
Acompanhe como várias cópias do mesmo atributo substituem umas às outras.

Use CaptureUnmatchedValues onde o desempenho de renderização de componentes


não é crítico, como componentes que não são repetidos com frequência. Para
componentes que são renderizados em escala, como cada item em uma lista grande ou
nas células de uma grade, tente evitar a modelagem de atributos.

Para obter mais informações, consulte componentes ASP.NET CoreRazor.

Implementar SetParametersAsync manualmente


Uma fonte significativa de sobrecarga de renderização por componente é gravar valores
de parâmetro de [Parameter] entrada em propriedades. O renderizador usa reflexão
para gravar os valores de parâmetro, o que pode levar a um desempenho ruim em
escala.

Em alguns casos extremos, talvez você queira evitar a reflexão e implementar sua
própria lógica de configuração de parâmetro manualmente. Isso pode ser aplicável
quando:

Um componente é renderizado com muita frequência, por exemplo, quando há


centenas ou milhares de cópias do componente na interface do usuário.
Um componente aceita muitos parâmetros.
Você descobre que a sobrecarga de recebimento de parâmetros tem um impacto
observável na capacidade de resposta da interface do usuário.

Em casos extremos, você pode substituir o método virtual SetParametersAsync do


componente e implementar sua própria lógica específica do componente. O exemplo a
seguir evita deliberadamente pesquisas de dicionário:

razor

@code {
[Parameter]
public int MessageId { get; set; }

[Parameter]
public string? Text { get; set; }

[Parameter]
public EventCallback<string> TextChanged { get; set; }

[Parameter]
public Theme CurrentTheme { get; set; }
public override Task SetParametersAsync(ParameterView parameters)
{
foreach (var parameter in parameters)
{
switch (parameter.Name)
{
case nameof(MessageId):
MessageId = (int)parameter.Value;
break;
case nameof(Text):
Text = (string)parameter.Value;
break;
case nameof(TextChanged):
TextChanged = (EventCallback<string>)parameter.Value;
break;
case nameof(CurrentTheme):
CurrentTheme = (Theme)parameter.Value;
break;
default:
throw new ArgumentException($"Unknown parameter:
{parameter.Name}");
}
}

return base.SetParametersAsync(ParameterView.Empty);
}
}

No código anterior, retornar a classe SetParametersAsync base executa os métodos


normais de ciclo de vida sem atribuir parâmetros novamente.

Como você pode ver no código anterior, substituir SetParametersAsync e fornecer


lógica personalizada é complicado e trabalhoso, portanto, geralmente não
recomendamos adotar essa abordagem. Em casos extremos, ele pode melhorar o
desempenho de renderização em 20-25%, mas você só deve considerar essa
abordagem nos cenários extremos listados anteriormente nesta seção.

Não disparar eventos muito rapidamente


Alguns eventos do navegador são disparados com muita frequência. Por exemplo,
onmousemove e onscroll pode disparar dezenas ou centenas de vezes por segundo. Na

maioria dos casos, você não precisa executar atualizações de interface do usuário com
frequência. Se os eventos forem disparados muito rapidamente, você poderá prejudicar
a capacidade de resposta da interface do usuário ou consumir tempo excessivo de CPU.

Em vez de usar eventos nativos que disparam rapidamente, considere o uso de JS


interoperabilidade para registrar um retorno de chamada que é acionado com menos
frequência. Por exemplo, o seguinte componente exibe a posição do mouse, mas só é
atualizado no máximo uma vez a cada 500 ms:

razor

@inject IJSRuntime JS
@implements IDisposable

<h1>@message</h1>

<div @ref="mouseMoveElement" style="border:1px dashed red;height:200px;">


Move mouse here
</div>

@code {
private ElementReference mouseMoveElement;
private DotNetObjectReference<MyComponent>? selfReference;
private string message = "Move the mouse in the box";

[JSInvokable]
public void HandleMouseMove(int x, int y)
{
message = $"Mouse move at {x}, {y}";
StateHasChanged();
}

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
selfReference = DotNetObjectReference.Create(this);
var minInterval = 500;

await JS.InvokeVoidAsync("onThrottledMouseMove",
mouseMoveElement, selfReference, minInterval);
}
}

public void Dispose() => selfReference?.Dispose();


}

O código JavaScript correspondente registra o ouvinte de eventos do DOM para


movimentação do mouse. Neste exemplo, o ouvinte de eventos usa a função de throttle
Lodash para limitar a taxa de invocações:

HTML

<script
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"
></script>
<script>
function onThrottledMouseMove(elem, component, interval) {
elem.addEventListener('mousemove', _.throttle(e => {
component.invokeMethodAsync('HandleMouseMove', e.offsetX, e.offsetY);
}, interval));
}
</script>

Evitar a rerendering após manipular eventos sem


alterações de estado
Por padrão, os componentes herdam de ComponentBase, que invoca StateHasChanged
automaticamente depois que os manipuladores de eventos do componente são
invocados. Em alguns casos, pode ser desnecessário ou indesejável disparar uma nova
geração depois que um manipulador de eventos é invocado. Por exemplo, um
manipulador de eventos pode não modificar o estado do componente. Nesses cenários,
o aplicativo pode aproveitar a IHandleEvent interface para controlar o comportamento
da manipulação de Blazoreventos.

Para evitar remetentes para todos os manipuladores de eventos de um componente,


implemente IHandleEvent e forneça uma IHandleEvent.HandleEventAsync tarefa que
invoque o manipulador de eventos sem chamar StateHasChanged.

No exemplo a seguir, nenhum manipulador de eventos adicionado ao componente


dispara uma nova geração, portanto HandleSelect , não resulta em uma nova geração
quando invocado.

Pages/HandleSelect1.razor :

razor

@page "/handle-select-1"
@using Microsoft.Extensions.Logging
@implements IHandleEvent
@inject ILogger<HandleSelect1> Logger

<p>
Last render DateTime: @dt
</p>

<button @onclick="HandleSelect">
Select me (Avoids Rerender)
</button>

@code {
private DateTime dt = DateTime.Now;

private void HandleSelect()


{
dt = DateTime.Now;

Logger.LogInformation("This event handler doesn't trigger a


rerender.");
}

Task IHandleEvent.HandleEventAsync(
EventCallbackWorkItem callback, object? arg) =>
callback.InvokeAsync(arg);
}

Além de impedir remetentes após os manipuladores de eventos serem acionados em


um componente de maneira global, é possível evitar remetentes após um único
manipulador de eventos empregando o método utilitário a seguir.

Adicione a classe a seguir EventUntil a um Blazor aplicativo. As ações e funções


estáticas na parte superior da EventUtil classe fornecem manipuladores que abrangem
várias combinações de argumentos e tipos de retorno que Blazor usam ao manipular
eventos.

EventUtil.cs :

C#

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;

public static class EventUtil


{
public static Action AsNonRenderingEventHandler(Action callback)
=> new SyncReceiver(callback).Invoke;
public static Action<TValue> AsNonRenderingEventHandler<TValue>(
Action<TValue> callback)
=> new SyncReceiver<TValue>(callback).Invoke;
public static Func<Task> AsNonRenderingEventHandler(Func<Task> callback)
=> new AsyncReceiver(callback).Invoke;
public static Func<TValue, Task> AsNonRenderingEventHandler<TValue>(
Func<TValue, Task> callback)
=> new AsyncReceiver<TValue>(callback).Invoke;

private record SyncReceiver(Action callback)


: ReceiverBase { public void Invoke() => callback(); }
private record SyncReceiver<T>(Action<T> callback)
: ReceiverBase { public void Invoke(T arg) => callback(arg); }
private record AsyncReceiver(Func<Task> callback)
: ReceiverBase { public Task Invoke() => callback(); }
private record AsyncReceiver<T>(Func<T, Task> callback)
: ReceiverBase { public Task Invoke(T arg) => callback(arg); }

private record ReceiverBase : IHandleEvent


{
public Task HandleEventAsync(EventCallbackWorkItem item, object arg)
=>
item.InvokeAsync(arg);
}
}

Chame EventUtil.AsNonRenderingEventHandler para chamar um manipulador de eventos


que não dispara uma renderização quando invocado.

No exemplo a seguir:

Selecionar o primeiro botão, que chama HandleClick1 , dispara uma nova geração.
Selecionar o segundo botão, que chama HandleClick2 , não dispara uma nova
geração.
Selecionar o terceiro botão, que chama HandleClick3 , não dispara uma nova
geração e usa argumentos de evento (MouseEventArgs).

Pages/HandleSelect2.razor :

razor

@page "/handle-select-2"
@using Microsoft.Extensions.Logging
@inject ILogger<HandleSelect2> Logger

<p>
Last render DateTime: @dt
</p>

<button @onclick="HandleClick1">
Select me (Rerenders)
</button>

<button @onclick="EventUtil.AsNonRenderingEventHandler(HandleClick2)">
Select me (Avoids Rerender)
</button>

<button @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>
(HandleClick3)">
Select me (Avoids Rerender and uses <code>MouseEventArgs</code>)
</button>

@code {
private DateTime dt = DateTime.Now;

private void HandleClick1()


{
dt = DateTime.Now;
Logger.LogInformation("This event handler triggers a rerender.");
}

private void HandleClick2()


{
dt = DateTime.Now;

Logger.LogInformation("This event handler doesn't trigger a


rerender.");
}

private void HandleClick3(MouseEventArgs args)


{
dt = DateTime.Now;

Logger.LogInformation(
"This event handler doesn't trigger a rerender. " +
"Mouse coordinates: {ScreenX}:{ScreenY}",
args.ScreenX, args.ScreenY);
}
}

Além de implementar a IHandleEvent interface , aproveitar as outras práticas


recomendadas descritas neste artigo também pode ajudar a reduzir renderizações
indesejadas depois que os eventos são tratados. Por exemplo, a substituição
ShouldRender em componentes filho do componente de destino pode ser usada para
controlar a rerendering.

Evite recriar delegados para muitos elementos ou


componentes repetidos
BlazorA recriação de delegados de expressão lambda para elementos ou componentes
em um loop pode levar a um desempenho ruim.

O componente a seguir mostrado no artigo de manipulação de eventos renderiza um


conjunto de botões. Cada botão atribui um delegado ao evento @onclick , o que é bom
se não houver muitos botões para renderizar:

razor

@page "/event-handler-example-5"

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)


{
var buttonNumber = i;
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}

@code {
private string heading = "Select a button to learn its position";

private void UpdateHeading(MouseEventArgs e, int buttonNumber)


{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}

Se um grande número de botões for renderizado usando a abordagem anterior, a


velocidade de renderização será afetada negativamente, levando a uma experiência de
usuário ruim. Para renderizar um grande número de botões com um retorno de
chamada para eventos de clique, o exemplo a seguir usa uma coleção de objetos de
botão que atribuem o delegado de @onclick cada botão a um Action. A abordagem a
seguir não exige Blazor a recompilação de todos os delegados de botão sempre que os
botões são renderizados:

Pages/LambdaEventPerformance.razor :

razor

@page "/lambda-event-performance"

<h1>@heading</h1>

@foreach (var button in Buttons)


{
<p>
<button @key="button.Id" @onclick="button.Action">
Button #@button.Id
</button>
</p>
}

@code {
private string heading = "Select a button to learn its position";

private List<Button> Buttons { get; set; } = new();

protected override void OnInitialized()


{
for (var i = 0; i < 100; i++)
{
var button = new Button();
button.Id = Guid.NewGuid().ToString();

button.Action = (e) =>


{
UpdateHeading(button, e);
};

Buttons.Add(button);
}
}

private void UpdateHeading(Button button, MouseEventArgs e)


{
heading = $"Selected #{button.Id} at {e.ClientX}:{e.ClientY}";
}

private class Button


{
public string? Id { get; set; }
public Action<MouseEventArgs> Action { get; set; } = e => { };
}
}

Otimizar a velocidade de interoperabilidade do


JavaScript
As chamadas entre o .NET e o JavaScript exigem sobrecarga adicional porque:

Por padrão, as chamadas são assíncronas.


Por padrão, os parâmetros e os valores retornados são JSserializados por ON para
fornecer um mecanismo de conversão fácil de entender entre os tipos .NET e
JavaScript.

Além disso, em Blazor Server, essas chamadas são passadas pela rede.

Evitar chamadas excessivamente refinadas


Como cada chamada envolve alguma sobrecarga, pode ser valioso reduzir o número de
chamadas. Considere o código a seguir, que armazena uma coleção de itens no do
localStorage navegador:

C#

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)


{
foreach (var item in items)
{
await JS.InvokeVoidAsync("localStorage.setItem", item.Id,
JsonSerializer.Serialize(item));
}
}

O exemplo anterior faz uma chamada de interoperabilidade separada JS para cada item.
Em vez disso, a abordagem a seguir reduz a JS interoperabilidade a uma única chamada:

C#

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)


{
await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}

A função JavaScript correspondente armazena toda a coleção de itens no cliente:

JavaScript

function storeAllInLocalStorage(items) {
items.forEach(item => {
localStorage.setItem(item.id, JSON.stringify(item));
});
}

Para Blazor WebAssembly aplicativos, a rolagem de chamadas de interoperabilidade


individuais JS em uma única chamada geralmente só melhora significativamente o
desempenho se o componente fizer um grande número de chamadas de JS
interoperabilidade.

Considere o uso de chamadas síncronas

Chamar JavaScript do .NET


Esta seção só se aplica a Blazor WebAssembly aplicativos.

As chamadas de interoperabilidade de JS são assíncronas por padrão,


independentemente do código chamado ser síncrono ou assíncrono. As chamadas são
assíncronas por padrão para garantir que os componentes sejam compatíveis entre os
modelos de hospedagem de Blazor, Blazor Server e Blazor WebAssembly. No Blazor
Server, todas as JS chamadas de interoperabilidade devem ser assíncronas porque são
enviadas por uma conexão de rede.
Se tiver certeza de que seu aplicativo só é executado no Blazor WebAssembly, você
pode optar por fazer chamadas de interoperabilidade síncronas JS . Isso tem um pouco
menos de sobrecarga do que fazer chamadas assíncronas e pode resultar em menos
ciclos de renderização porque não há estado intermediário enquanto aguarda
resultados.

Para fazer uma chamada síncrona do .NET para o JavaScript em um Blazor


WebAssembly aplicativo, converta IJSRuntime para IJSInProcessRuntime para fazer a JS
chamada de interoperabilidade:

razor

@inject IJSRuntime JS

...

@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>
("javascriptFunctionIdentifier");
}
}

Ao trabalhar com IJSObjectReference aplicativos ASP.NET Core 5.0 ou posterioresBlazor


WebAssembly, você pode usar IJSInProcessObjectReference de forma síncrona:

C#

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import",


"./scripts.js");

Chamar .NET do JavaScript


Esta seção só se aplica a Blazor WebAssembly aplicativos.

As chamadas de interoperabilidade de JS são assíncronas por padrão,


independentemente do código chamado ser síncrono ou assíncrono. As chamadas são
assíncronas por padrão para garantir que os componentes sejam compatíveis entre os
modelos de hospedagem de Blazor, Blazor Server e Blazor WebAssembly. No Blazor
Server, todas as JS chamadas de interoperabilidade devem ser assíncronas porque são
enviadas por uma conexão de rede.

Se tiver certeza de que seu aplicativo só é executado no Blazor WebAssembly, você


pode optar por fazer chamadas de interoperabilidade síncronas JS . Isso tem um pouco
menos de sobrecarga do que fazer chamadas assíncronas e pode resultar em menos
ciclos de renderização porque não há estado intermediário enquanto aguarda
resultados.

Para fazer uma chamada síncrona do JavaScript para o .NET em Blazor WebAssembly
aplicativos, use DotNet.invokeMethod em vez de DotNet.invokeMethodAsync .

Chamadas síncronas funcionarão se:

O aplicativo está em execução em Blazor WebAssembly, não Blazor Serverem .


A função chamada retorna um valor de forma síncrona. A função não é um async
método e não retorna um .NET Task ou JavaScript Promise .

Usar interoperabilidade do JavaScript


[JSImport] / [JSExport]

A interoperabilidade JavaScript [JSImport] / [JSExport] para Blazor WebAssembly


aplicativos oferece melhor desempenho e estabilidade em relação à JS API de
interoperabilidade em versões de estrutura antes do ASP.NET Core 7.0.

Para obter mais informações, consulte Interoperabilidade de Importação/JSExportação


do JavaScript JScom ASP.NET Core Blazor WebAssembly.

Compilação AOT (antecipada)


A compilação AOT (antecipada) compila o código .NET de um Blazor aplicativo
diretamente no WebAssembly nativo para execução direta pelo navegador. Aplicativos
compilados por AOT resultam em aplicativos maiores que levam mais tempo para serem
baixados, mas os aplicativos compilados por AOT geralmente fornecem melhor
desempenho de runtime, especialmente para aplicativos que executam tarefas com uso
intensivo de CPU. Para obter mais informações, consulte Hospedar e implantar ASP.NET
Core Blazor WebAssembly.

Minimizar o tamanho do download do


aplicativo
Revinculação de runtime
Para obter informações sobre como a revinculação de runtime minimiza o tamanho do
download de um aplicativo, consulte Hospedar e implantar ASP.NET Core Blazor
WebAssembly.

Use System.Text.Json .
BlazorA implementação de interoperabilidade depende System.Text.Jsonde JS , que é
uma biblioteca de serialização ON de alto desempenho JScom baixa alocação de
memória. Usar System.Text.Json não deve resultar em tamanho adicional de carga do
aplicativo ao adicionar uma ou mais bibliotecas ON alternativas JS.

Para obter diretrizes de migração, consulte Como migrar de Newtonsoft.Json para


System.Text.Json.

Corte de IL (Linguagem Intermediária)


Esta seção só se aplica a Blazor WebAssembly aplicativos.

Cortar assemblies não utilizados de um Blazor WebAssembly aplicativo reduz o


tamanho do aplicativo removendo o código não utilizado nos binários do aplicativo.
Para obter mais informações, consulte Configure the Trimmer for ASP.NET Core Blazor.

Assemblies de carga lentos


Esta seção só se aplica a Blazor WebAssembly aplicativos.

Carregue assemblies em runtime quando os assemblies forem exigidos por uma rota.
Para obter mais informações, consulte Assemblies de carregamento lentos no ASP.NET
Core Blazor WebAssembly.

Compactação
Esta seção só se aplica a Blazor WebAssembly aplicativos.

Quando um Blazor WebAssembly aplicativo é publicado, a saída é compactada


estaticamente durante a publicação para reduzir o tamanho do aplicativo e remover a
sobrecarga para compactação de runtime. Blazor depende do servidor para executar a
negociação de conteúdo e fornecer arquivos compactados estaticamente.
Depois que um aplicativo for implantado, verifique se o aplicativo atende arquivos
compactados. Inspecione a guia Rede nas ferramentas de desenvolvedor de um
navegador e verifique se os arquivos são atendidos com Content-Encoding: br
(compactação Brotli) ou Content-Encoding: gz (compactação Gzip). Se o host não
estiver servindo arquivos compactados, siga as instruções em Host e implante ASP.NET
Core Blazor WebAssembly.

Desabilitar recursos não utilizados


Esta seção só se aplica a Blazor WebAssembly aplicativos.

Blazor WebAssemblyO runtime do inclui os seguintes recursos do .NET que podem ser
desabilitados para um tamanho de carga menor:

Um arquivo de dados é incluído para corrigir as informações de fuso horário. Se o


aplicativo não exigir esse recurso, considere desabilitá-lo definindo a
BlazorEnableTimeZoneSupport propriedade MSBuild no arquivo de projeto do

aplicativo como false :

XML

<PropertyGroup>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
</PropertyGroup>

Por padrão, Blazor WebAssembly o carrega recursos de globalização necessários


para exibir valores, como datas e moeda, na cultura do usuário. Se o aplicativo não
exigir localização, você poderá configurar o aplicativo para dar suporte à cultura
invariável, que é baseada na en-US cultura.
Testar Razor componentes no ASP.NET
Core Blazor
Artigo • 10/01/2023 • 19 minutos para o fim da leitura

Por: Egil Hansen

Testar Razor componentes é um aspecto importante da liberação de aplicativos estáveis


e de Blazor manutenção.

Para testar um Razor componente, o componente em teste (CUT) é:

Renderizado com entrada relevante para o teste.


Dependendo do tipo de teste executado, possivelmente sujeito à interação ou
modificação. Por exemplo, manipuladores de eventos podem ser disparados, como
um onclick evento para um botão.
Inspecionado quanto aos valores esperados. Um teste é aprovado quando um ou
mais valores inspecionados correspondem aos valores esperados para o teste.

Abordagens de teste
Duas abordagens comuns para componentes de teste Razor são testes de ponta a
ponta (E2E) e testes de unidade:

Teste de unidade: os testes de unidade são escritos com uma biblioteca de testes
de unidade que fornece:
Renderização de componente.
Inspeção da saída e do estado do componente.
Acionamento de manipuladores de eventos e métodos de ciclo de vida.
Declarações de que o comportamento do componente está correto.

bUnit é um exemplo de uma biblioteca que habilita o Razor teste de unidade de


componente.

Teste E2E: um executor de teste executa um Blazor aplicativo que contém CUT e
automatiza uma instância do navegador. A ferramenta de teste inspeciona e
interage com a CUT por meio do navegador. O Playwright para .NET é um
exemplo de uma estrutura de teste E2E que pode ser usada com Blazor aplicativos.

No teste de unidade, somente o Razor componente (Razor/C#) está envolvido.


Dependências externas, como serviços e JS interoperabilidade, devem ser simuladas. No
teste E2E, o Razor componente e toda a infraestrutura auxiliar fazem parte do teste,
incluindo CSS, JSe as APIs do DOM e do navegador.

O escopo do teste descreve o quão extensos são os testes. O escopo de teste


normalmente tem uma influência na velocidade dos testes. Os testes de unidade são
executados em um subconjunto dos subsistemas do aplicativo e geralmente são
executados em milissegundos. Os testes E2E, que testam um amplo grupo de
subsistemas do aplicativo, podem levar vários segundos para serem concluídos.

O teste de unidade também fornece acesso à instância do CUT, permitindo a inspeção e


a verificação do estado interno do componente. Normalmente, isso não é possível no
teste E2E.

Em relação ao ambiente do componente, os testes E2E devem garantir que o estado


ambiental esperado tenha sido atingido antes do início da verificação. Caso contrário, o
resultado será imprevisível. No teste de unidade, a renderização do CUT e o ciclo de
vida do teste são mais integrados, o que melhora a estabilidade do teste.

O teste E2E envolve a inicialização de vários processos, E/S de rede e disco e outras
atividades de subsistema que geralmente levam a uma confiabilidade de teste ruim. Os
testes de unidade normalmente são isolados desses tipos de problemas.

A tabela a seguir resume a diferença entre as duas abordagens de teste.

Funcionalidade Teste de unidade Teste E2E

Escopo de teste Razor somente component Razor component (Razor/C#)


(Razor/C#) com CSS/JS

Tempo de execução do Milissegundos Segundos


teste

Acesso à instância do Yes Não


componente

Confidencial para o Não Yes


ambiente

Confiabilidade Mais confiável Menos confiável

Escolha a abordagem de teste mais apropriada


Considere o cenário ao escolher o tipo de teste a ser executado. Algumas considerações
são descritas na tabela a seguir.
Cenário Abordagem Comentários
sugerida

Componente sem Teste de Quando não há dependência de JS interoperabilidade em um


JS lógica de unidade Razor componente, o componente pode ser testado sem
interoperabilidade acesso ou à JS API do DOM. Nesse cenário, não há
desvantagens na escolha do teste de unidade.

Componente com Teste de É comum que os componentes consultem o DOM ou


lógica de unidade disparem animações por meio JS da interoperabilidade. O
interoperabilidade teste de unidade geralmente é preferencial nesse cenário,
simples JS pois é simples simular a JS interação por meio da IJSRuntime
interface .

Componente que Teste de Se um componente usar JS interoperabilidade para chamar


depende de unidade e uma biblioteca grande ou complexa JS , mas a interação
código complexo testes entre o Razor componente e JS a biblioteca for simples, a
JS separados melhor abordagem provavelmente tratará o componente e JS
JS a biblioteca ou o código como duas partes separadas e
testará cada uma individualmente. Teste o Razor componente
com uma biblioteca de teste de unidade e teste o JS com
uma JS biblioteca de testes.

Componente com Teste E2E Quando a funcionalidade de um componente depende JS de


lógica que e de sua manipulação do DOM, verifique o JS código e Blazor
depende JS da juntos em um teste E2E. Essa é a abordagem que os
manipulação do desenvolvedores da estrutura usaram com Blazora Blazor
DOM do lógica de renderização do navegador, que tem C# e JS
navegador código firmemente acoplados. O C# e JS o código devem
trabalhar juntos para renderizar Razor corretamente os
componentes em um navegador.

Componente que Teste E2E Quando a funcionalidade de um componente depende de


depende da uma biblioteca de classes de terceiros que tem dependências
biblioteca de difíceis de simular, como JS interoperabilidade, o teste E2E
classes de pode ser a única opção para testar o componente.
terceiros com
dependências
difíceis de simular

Testar componentes com bUnit


Não há nenhuma estrutura de teste oficial da Microsoft para Blazoro , mas o projeto
orientado pela comunidade bUnit fornece uma maneira conveniente de testar Razor
componentes.

7 Observação
bUnit é uma biblioteca de testes de terceiros e não tem suporte ou é mantida pela
Microsoft.

O bUnit funciona com estruturas de teste de uso geral, como MSTest, NUnit e xUnit .
Essas estruturas de teste fazem com que os testes bUnit pareçam testes de unidade
regulares. Os testes bUnit integrados a uma estrutura de teste de uso geral
normalmente são executados com:

Gerenciador de Testes do Visual Studio.


dotnet test Comando da CLI em um shell de comando.
Um pipeline de teste automatizado do DevOps.

7 Observação

Os conceitos de teste e as implementações de teste em diferentes estruturas de


teste são semelhantes, mas não idênticos. Consulte a documentação da estrutura
de teste para obter diretrizes.

O exemplo a seguir demonstra a estrutura de um teste bUnit no Counter componente


em um aplicativo com base em um Blazor modelo de projeto. O Counter componente
exibe e incrementa um contador com base no usuário selecionando um botão na
página:

razor

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

O teste bUnit a seguir verifica se o contador cut é incrementado corretamente quando o


botão é selecionado:
C#

[Fact]
public void CounterShouldIncrementWhenSelected()
{
// Arrange
using var ctx = new TestContext();
var cut = ctx.RenderComponent<Counter>();
var paraElm = cut.Find("p");

// Act
cut.Find("button").Click();
var paraElmText = paraElm.TextContent;

// Assert
paraElmText.MarkupMatches("Current count: 1");
}

As seguintes ações ocorrem em cada etapa do teste:

Organizar: o Counter componente é renderizado usando o de TestContext bUnit.


O elemento de parágrafo CUT ( <p> ) é encontrado e atribuído a paraElm .

Agir: o elemento do botão ( <button> ) está localizado e, em seguida, selecionado


chamando Click , que deve incrementar o contador e atualizar o conteúdo da
marca de parágrafo ( <p> ). O conteúdo do texto do elemento paragraph é obtido
chamando TextContent .

Assert: MarkupMatches é chamado no conteúdo do texto para verificar se ele


corresponde à cadeia de caracteres esperada, que é Current count: 1 .

7 Observação

O MarkupMatches método assert difere de uma asserção de comparação de cadeia


de caracteres regular (por exemplo, Assert.Equal("Current count: 1",
paraElmText); ) MarkupMatches executa uma comparação semântica da entrada e da

marcação HTML esperada. Uma comparação semântica está ciente da semântica


HTML, o que significa que coisas como espaço em branco insignificante são
ignoradas. Isso resulta em testes mais estáveis. Para obter mais informações,
consulte Personalizando a comparação HTML semântica .

Recursos adicionais
Introdução com bUnit : as instruções bUnit incluem diretrizes sobre como criar
um projeto de teste, referenciar pacotes de estrutura de teste e criar e executar
testes.
Blazor PWA (Aplicativo Web Progressivo
ASP.NET Core)
Artigo • 28/11/2022 • 82 minutos para o fim da leitura

Um Blazor PWA (Aplicativo Web Progressivo) é um SPA (aplicativo de página única) que
usa APIs de navegador modernas e recursos para se comportar como um aplicativo da
área de trabalho.

Blazor WebAssembly é uma plataforma de aplicativo Web do lado do cliente baseada


em padrões, portanto, ela pode usar qualquer API do navegador, incluindo APIs PWA
necessárias para os seguintes recursos:

Trabalhando offline e carregando instantaneamente, independentemente da


velocidade da rede.
Em execução em sua própria janela de aplicativo, não apenas em uma janela do
navegador.
Sendo iniciado no menu iniciar do sistema operacional do host, no encaixe ou na
tela inicial.
Recebendo notificações por push de um servidor de back-end, mesmo quando o
usuário não está usando o aplicativo.
Atualização automática em segundo plano.

A palavra progressiva é usada para descrever esses aplicativos porque:

Um usuário pode primeiro descobrir e usar o aplicativo em seu navegador da Web


como qualquer outro SPA.
Posteriormente, o usuário progride para instalá-lo em seu sistema operacional e
habilitar notificações por push.

Criar um projeto com base no modelo PWA


Visual Studio

Ao criar um novo Blazor WebAssembly Aplicativo, marque a caixa de seleção


Aplicativo Web Progressivo .

Opcionalmente, o PWA pode ser configurado para um aplicativo criado a partir do


modelo hospedado ASP.NET Core. O cenário de PWA é independente do modelo de
hospedagem.
Converter um aplicativo existente Blazor
WebAssembly em um PWA
Converta um aplicativo existente Blazor WebAssembly em um PWA seguindo as
diretrizes nesta seção.

No arquivo de projeto do aplicativo:

Adicione a seguinte ServiceWorkerAssetsManifest propriedade a um


PropertyGroup :

XML

...
<ServiceWorkerAssetsManifest>service-worker-
assets.js</ServiceWorkerAssetsManifest>
</PropertyGroup>

Adicione o seguinte ServiceWorker item a um ItemGroup :

XML

<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js"
PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>

Para obter ativos estáticos, use uma das seguintes abordagens:

Crie um novo projeto PWA separado com o dotnet new comando em um shell de
comando:

CLI do .NET

dotnet new blazorwasm -o MyBlazorPwa --pwa

No comando anterior, a opção -o|--output cria uma nova pasta para o aplicativo
chamado MyBlazorPwa .

Se você não estiver convertendo um aplicativo para a versão mais recente, passe
a opção -f|--framework . O exemplo a seguir cria o aplicativo para ASP.NET Core
versão 3.1:

CLI do .NET
dotnet new blazorwasm -o MyBlazorPwa --pwa -f netcoreapp3.1

Navegue até o ASP.NET Core repositório GitHub na URL a seguir, que é vinculada à
origem e aos main ativos de referência de branch. Selecione a versão com a qual
você está trabalhando na lista suspensa Alternar branches ou marcas que se
aplica ao seu aplicativo.

Blazor WebAssembly pasta de modelo wwwroot de projeto (branch do repositório


main GitHub dotnet/aspnetcore)

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente


carregam o branch padrão do repositório, que representa o desenvolvimento
atual da próxima versão do .NET. Para selecionar uma marca para uma versão
específica, use a lista suspensa para Alternar branches ou marcas. Para saber
mais, confira Como selecionar uma marca de versão do código-fonte do
ASP.NET Core (dotnet/AspNetCore.Docs #26205) .

Na pasta de origem wwwroot no aplicativo que você criou ou nos ativos de referência no
dotnet/aspnetcore repositório GitHub, copie os seguintes arquivos para a pasta do

wwwroot aplicativo:

icon-512.png
manifest.json

service-worker.js
service-worker.published.js

No arquivo do wwwroot/index.html aplicativo:

Adicione <link> elementos para o manifesto e o ícone do aplicativo:

HTML

<link href="manifest.json" rel="manifest" />


<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />

Adicione a seguinte <script> marca dentro da marca de fechamento </body>


imediatamente após a marca de blazor.webassembly.js script:

HTML
...
<script>navigator.serviceWorker.register('service-worker.js');
</script>
</body>

Instalação e manifesto do aplicativo


Ao visitar um aplicativo criado usando o modelo PWA, os usuários têm a opção de
instalar o aplicativo no menu iniciar do sistema operacional, no encaixe ou na tela inicial.
A forma como essa opção é apresentada depende do navegador do usuário. Ao usar
navegadores baseados em Chromium da área de trabalho, como Edge ou Chrome, um
botão Adicionar aparece na barra de URL. Depois que o usuário selecionar o botão
Adicionar , ele receberá uma caixa de diálogo de confirmação:

No iOS, os visitantes podem instalar o PWA usando o botão Compartilhar do Safari e


sua opção Adicionar à Hometela . No Chrome para Android, os usuários devem
selecionar o botão Menu no canto superior direito, seguido por Adicionar à Home tela.

Depois de instalado, o aplicativo aparece em sua própria janela sem uma barra de
endereços:
Para personalizar o título da janela, o esquema de cores, o ícone ou outros detalhes,
consulte o manifest.json arquivo no diretório do wwwroot projeto. O esquema desse
arquivo é definido pelos padrões da Web. Para obter mais informações, consulte MDN
Web docs: Manifesto do Aplicativo Web .

Suporte offline
Por padrão, os aplicativos criados usando a opção de modelo PWA têm suporte para
execução offline. Um usuário deve primeiro visitar o aplicativo enquanto estiver online.
O navegador baixa e armazena em cache automaticamente todos os recursos
necessários para operar offline.

) Importante

O suporte ao desenvolvimento interferiria no ciclo de desenvolvimento usual de


fazer alterações e testá-las. Portanto, o suporte offline só está habilitado para
aplicativos publicados .

2 Aviso

Se você pretende distribuir um PWA habilitado para offline, há vários avisos e


advertências importantes. Esses cenários são inerentes a PWAs offline e não são
específicos para Blazor. Certifique-se de ler e entender essas advertências antes de
fazer suposições sobre como seu aplicativo habilitado para offline funciona.

Para ver como funciona o suporte offline:

1. Publique o aplicativo. Para obter mais informações, consulte Hospedar e implantar


ASP.NET Core Blazor.

2. Implante o aplicativo em um servidor que dê suporte a HTTPS e acesse o aplicativo


em um navegador em seu endereço HTTPS seguro.

3. Abra as ferramentas de desenvolvimento do navegador e verifique se um Service


Worker está registrado para o host na guia Aplicativo :

4. Recarregue a página e examine a guia Rede . O Service Worker ou o cache de


memória são listados como as fontes de todos os ativos da página:

5. Para verificar se o navegador não depende do acesso à rede para carregar o


aplicativo, também:

Desligue o servidor Web e veja como o aplicativo continua funcionando


normalmente, o que inclui recarregamentos de página. Da mesma forma, o
aplicativo continua funcionando normalmente quando há uma conexão de
rede lenta.
Instrua o navegador a simular o modo offline na guia Rede :

O suporte offline usando um trabalho de serviço é um padrão da Web, não específico


para Blazor. Para obter mais informações sobre os funcionários do serviço, consulte
Documentos da Web MDN: API do Service Worker . Para saber mais sobre padrões de
uso comuns para os trabalhadores do serviço, consulte Google Web: O Ciclo de Vida do
Service Worker .

BlazorO modelo PWA produz dois arquivos de trabalho de serviço:

wwwroot/service-worker.js , que é usado durante o desenvolvimento.

wwwroot/service-worker.published.js , que é usado após a publicação do


aplicativo.

Para compartilhar a lógica entre os dois arquivos de trabalho de serviço, considere a


seguinte abordagem:

Adicione um terceiro arquivo JavaScript para manter a lógica comum.


Use self.importScripts para carregar a lógica comum em ambos os arquivos de
trabalho de serviço.

Estratégia de busca em cache


O trabalho de serviço interno service-worker.published.js resolve solicitações usando
uma estratégia de primeiro cache . Isso significa que o service worker prefere retornar
conteúdo armazenado em cache, independentemente de o usuário ter acesso à rede ou
se o conteúdo mais recente está disponível no servidor.

A estratégia de primeiro cache é valiosa porque:

Ele garante a confiabilidade. O acesso à rede não é um estado booliano. Um


usuário não está simplesmente online ou offline:
O dispositivo do usuário pode assumir que está online, mas a rede pode ser tão
lenta quanto ser impraticável de esperar.
A rede pode retornar resultados inválidos para determinadas URLs, como
quando há um portal WIFI cativo que está bloqueando ou redirecionando
determinadas solicitações no momento.

É por isso que a API do navigator.onLine navegador não é confiável e não deve
ser dependente.

Ele garante a correção. Ao criar um cache de recursos offline, o trabalhador do


serviço usa o hash de conteúdo para garantir que ele tenha buscado um
instantâneo completo e auto-consistente dos recursos em um único instante no
tempo. Esse cache é então usado como uma unidade atômica. Não adianta pedir
recursos mais recentes à rede, pois as únicas versões necessárias são as já
armazenadas em cache. Qualquer outra coisa corre o risco de inconsistência e
incompatibilidade (por exemplo, tentar usar versões de assemblies .NET que não
foram compilados juntos).

Se você precisar impedir que o navegador busque service-worker-assets.js seu cache


HTTP, por exemplo, para resolver falhas temporárias de verificação de integridade ao
implantar uma nova versão do trabalho de serviço, atualize o registro do service worker
em wwwroot/index.html com definido como updateViaCache 'none':

HTML

<script>
navigator.serviceWorker.register('/service-worker.js', {updateViaCache:
'none'});
</script>

Atualizações em segundo plano


Como um modelo mental, você pode pensar em um PWA offline-first como se
comportando como um aplicativo móvel que pode ser instalado. O aplicativo é iniciado
imediatamente, independentemente da conectividade de rede, mas a lógica do
aplicativo instalada vem de um instantâneo pontual que pode não ser a versão mais
recente.

O Blazor modelo PWA produz aplicativos que tentam se atualizar automaticamente em


segundo plano sempre que o usuário visita e tem uma conexão de rede em
funcionamento. A maneira como isso funciona é a seguinte:

Durante a compilação, o projeto gera um manifesto de ativos do service worker. Por


padrão, isso é chamado service-worker-assets.js de . O manifesto lista todos os
recursos estáticos que o aplicativo requer para funcionar offline, como assemblies
.NET, arquivos JavaScript e CSS, incluindo seus hashes de conteúdo. A lista de
recursos é carregada pelo trabalho de serviço para que ele saiba quais recursos
armazenar em cache.
Sempre que o usuário visita o aplicativo, o navegador solicita novamente service-
worker.js e service-worker-assets.js em segundo plano. Os arquivos são

comparados byte por byte com o trabalho de serviço instalado existente. Se o


servidor retornar conteúdo alterado para qualquer um desses arquivos, o
trabalhador do serviço tentará instalar uma nova versão de si mesmo.
Ao instalar uma nova versão de si mesmo, o trabalho de serviço cria um cache
novo e separado para recursos offline e começa a preencher o cache com recursos
listados em service-worker-assets.js . Essa lógica é implementada na função
dentro service-worker.published.js de onInstall .
O processo é concluído com êxito quando todos os recursos são carregados sem
erro e todos os hashes de conteúdo correspondem. Se tiver êxito, o novo trabalho
de serviço entrará em um estado de espera para ativação . Assim que o usuário
fecha o aplicativo (sem guias ou janelas de aplicativo restantes), o novo trabalho
de serviço fica ativo e é usado para visitas subsequentes ao aplicativo. O
trabalhador de serviço antigo e seu cache são excluídos.
Se o processo não for concluído com êxito, a nova instância de trabalho de serviço
será descartada. O processo de atualização é tentado novamente na próxima visita
do usuário, quando esperamos que o cliente tenha uma conexão de rede melhor
que possa concluir as solicitações.

Personalize esse processo editando a lógica do trabalho de serviço. Nenhum dos


comportamentos anteriores é específico, Blazor mas é apenas a experiência padrão
fornecida pela opção de modelo PWA. Para obter mais informações, consulte MDN Web
docs: API do Service Worker .

Como as solicitações são resolvidas


Conforme descrito na seção Estratégia de busca do cache primeiro , o trabalhador de
serviço padrão usa uma estratégia de cache primeiro , o que significa que ele tenta servir
conteúdo armazenado em cache quando disponível. Se não houver conteúdo
armazenado em cache para uma determinada URL, por exemplo, ao solicitar dados de
uma API de back-end, o trabalhador do serviço retornará em uma solicitação de rede
regular. A solicitação de rede terá êxito se o servidor estiver acessível. Essa lógica é
implementada dentro onFetch da função dentro de service-worker.published.js .

Se os componentes do aplicativo dependem da Razor solicitação de dados de APIs de


back-end e você deseja fornecer uma experiência amigável do usuário para solicitações
com falha devido à indisponibilidade da rede, implemente a lógica dentro dos
componentes do aplicativo. Por exemplo, use em torno HttpClient de try/catch
solicitações.

Dar suporte a páginas renderizadas pelo servidor


Considere o que acontece quando o usuário navega pela primeira vez para uma URL,
como /counter ou qualquer outro link profundo no aplicativo. Nesses casos, você não
deseja retornar o conteúdo armazenado em cache como /counter , mas, em vez disso,
precisa do navegador para carregar o conteúdo armazenado em cache para
/index.html iniciar seu Blazor WebAssembly aplicativo. Essas solicitações iniciais são

conhecidas como solicitações de navegação , em vez de:

subresource solicitações de imagens, folhas de estilos ou outros arquivos.


fetch/XHR solicitações de dados de API.

O trabalho de serviço padrão contém lógica de caso especial para solicitações de


navegação. O service worker resolve as solicitações retornando o conteúdo armazenado
em cache para /index.html , independentemente da URL solicitada. Essa lógica é
implementada na função dentro service-worker.published.js de onFetch .

Se seu aplicativo tiver determinadas URLs que devem retornar HTML renderizado pelo
servidor e não servir /index.html do cache, você precisará editar a lógica em seu
trabalho de serviço. Se todas as URLs que contêm /Identity/ precisarem ser tratadas
como solicitações somente online regulares para o servidor, modifique service-
worker.published.js onFetch a lógica. Localize o código a seguir:

JavaScript

const shouldServeIndexHtml = event.request.mode === 'navigate';


Altere o código para o seguinte:

JavaScript

const shouldServeIndexHtml = event.request.mode === 'navigate'


&& !event.request.url.includes('/Identity/');

Se você não fizer isso, independentemente da conectividade de rede, o service worker


intercepta as solicitações dessas URLs e as resolve usando /index.html .

Adicione pontos de extremidade adicionais para provedores de autenticação externa à


verificação. No exemplo a seguir, /signin-google para a autenticação do Google é
adicionado à verificação:

JavaScript

const shouldServeIndexHtml = event.request.mode === 'navigate'


&& !event.request.url.includes('/Identity/')
&& !event.request.url.includes('/signin-google');

Nenhuma ação é necessária para o ambiente de desenvolvimento, em que o conteúdo


sempre é buscado da rede.

Controlar o cache de ativos


Se o projeto definir a propriedade MSBuild, Blazoras ServiceWorkerAssetsManifest
ferramentas de build gerarão um manifesto de ativos de trabalho de serviço com o
nome especificado. O modelo PWA padrão produz um arquivo de projeto que contém a
seguinte propriedade:

XML

<ServiceWorkerAssetsManifest>service-worker-
assets.js</ServiceWorkerAssetsManifest>

O arquivo é colocado no wwwroot diretório de saída, para que o navegador possa


recuperar esse arquivo solicitando /service-worker-assets.js . Para ver o conteúdo
desse arquivo, abra /bin/Debug/{TARGET FRAMEWORK}/wwwroot/service-worker-assets.js
em um editor de texto. No entanto, não edite o arquivo, pois ele é regenerado em cada
build.

Por padrão, este manifesto lista:


Todos Blazoros recursos gerenciados, como assemblies .NET e os arquivos de
runtime do .NET WebAssembly necessários para funcionar offline.
Todos os recursos para publicação no diretório do wwwroot aplicativo, como
imagens, folhas de estilos e arquivos JavaScript, incluindo ativos da Web estáticos
fornecidos por projetos externos e pacotes NuGet.

Você pode controlar quais desses recursos são buscados e armazenados em cache pelo
trabalho de serviço editando a lógica em onInstall service-worker.published.js . Por
padrão, o service worker busca e armazena em cache arquivos que correspondem a
extensões típicas de nome de arquivo da Web, como .html , .css , .js e .wasm , além de
tipos de arquivo específicos para Blazor WebAssembly ( .dll , .pdb ).

Para incluir recursos adicionais que não estão presentes no diretório do aplicativo,
defina entradas extras do wwwroot MSBuild ItemGroup , conforme mostrado no exemplo
a seguir:

XML

<ItemGroup>
<ServiceWorkerAssetsManifestItem Include="MyDirectory\AnotherFile.json"
RelativePath="MyDirectory\AnotherFile.json"
AssetUrl="files/AnotherFile.json" />
</ItemGroup>

Os AssetUrl metadados especificam a URL relativa à base que o navegador deve usar
ao buscar o recurso para armazenar em cache. Isso pode ser independente do nome do
arquivo de origem original no disco.

) Importante

Adicionar um ServiceWorkerAssetsManifestItem não faz com que o arquivo seja


publicado no diretório do wwwroot aplicativo. A saída de publicação deve ser
controlada separadamente. O ServiceWorkerAssetsManifestItem só faz com que
uma entrada adicional apareça no manifesto de ativos do service worker.

Notificações por push


Como qualquer outro PWA, um Blazor WebAssembly PWA pode receber notificações
por push de um servidor de back-end. O servidor pode enviar notificações por push a
qualquer momento, mesmo quando o usuário não está usando ativamente o aplicativo.
Por exemplo, as notificações por push podem ser enviadas quando um usuário diferente
executa uma ação relevante.

O mecanismo para enviar uma notificação por push é totalmente independente de


Blazor WebAssembly, já que ele é implementado pelo servidor de back-end que pode
usar qualquer tecnologia. Se você quiser enviar notificações por push de um servidor
ASP.NET Core, considere usar uma técnica semelhante à abordagem feita na oficina
Blazing Pizza .

O mecanismo para receber e exibir uma notificação por push no cliente também é
independente de Blazor WebAssembly, já que ele é implementado no arquivo JavaScript
do service worker. Para obter um exemplo, consulte a abordagem usada na oficina
Blazing Pizza .

Advertências para PWAs offline


Nem todos os aplicativos devem tentar dar suporte ao uso offline. O suporte offline
adiciona complexidade significativa, embora nem sempre seja relevante para os casos
de uso necessários.

O suporte offline geralmente é relevante apenas:

Se o repositório de dados primário for local para o navegador. Por exemplo, a


abordagem é relevante em um aplicativo com uma interface do usuário para um
dispositivo IoT que armazena dados em localStorage ou IndexedDB .
Se o aplicativo executar uma quantidade significativa de trabalho para buscar e
armazenar em cache os dados da API de back-end relevantes para cada usuário
para que ele possa navegar pelos dados offline. Se o aplicativo precisar dar
suporte à edição, um sistema para acompanhar alterações e sincronizar dados com
o back-end deverá ser criado.
Se o objetivo for garantir que o aplicativo seja carregado imediatamente,
independentemente das condições de rede. Implemente uma experiência de
usuário adequada em torno de solicitações de API de back-end para mostrar o
progresso das solicitações e se comportar normalmente quando as solicitações
falham devido à indisponibilidade da rede.

Além disso, PWAs com capacidade offline devem lidar com uma série de complicações
adicionais. Os desenvolvedores devem se familiarizar cuidadosamente com as
advertências nas seções a seguir.

Suporte offline somente quando publicado


Durante o desenvolvimento, você normalmente deseja ver cada alteração refletida
imediatamente no navegador sem passar por um processo de atualização em segundo
plano. Portanto, Blazoro modelo PWA habilita o suporte offline somente quando
publicado.

Ao criar um aplicativo com capacidade offline, não é suficiente testar o aplicativo no


ambiente de desenvolvimento. Você deve testar o aplicativo em seu estado publicado
para entender como ele responde a diferentes condições de rede.

Atualizar a conclusão após a navegação do usuário longe


do aplicativo
Atualizações não concluir até que o usuário navegue para longe do aplicativo em todas
as guias. Conforme explicado na seção Atualizações em segundo plano , depois que
você implanta uma atualização no aplicativo, o navegador busca os arquivos de
trabalho de serviço atualizados para iniciar o processo de atualização.

O que surpreende muitos desenvolvedores é que, mesmo quando essa atualização é


concluída, ela não entra em vigor até que o usuário tenha navegado para longe em
todas as guias. Não é suficiente atualizar a guia exibindo o aplicativo, mesmo que seja a
única guia que exibe o aplicativo. Até que seu aplicativo seja completamente fechado, o
novo trabalho de serviço permanecerá aguardando para ativar o status. Isso não é
específico para Blazor, mas é um comportamento padrão da plataforma Web.

Isso geralmente incomoda os desenvolvedores que estão tentando testar atualizações


para seu trabalho de serviço ou recursos armazenados em cache offline. Se você fizer
check-in das ferramentas de desenvolvedor do navegador, poderá ver algo semelhante
ao seguinte:
Enquanto a lista de "clientes", que são guias ou janelas que exibem seu aplicativo, não
estiver vazia, o trabalhador continuará aguardando. O motivo pelo qual os
trabalhadores do serviço fazem isso é para garantir a consistência. Consistência significa
que todos os recursos são buscados do mesmo cache atômico.

Ao testar alterações, talvez seja conveniente clicar no link "skipWaiting", conforme


mostrado na captura de tela anterior, e recarregar a página. Você pode automatizar isso
para todos os usuários codificando seu trabalho de serviço para ignorar a fase de
"espera" e ativar imediatamente na atualização . Se você ignorar a fase de espera,
estará abrindo mão da garantia de que os recursos são sempre buscados
consistentemente da mesma instância de cache.

Os usuários podem executar qualquer versão histórica do


aplicativo
Os desenvolvedores da Web normalmente esperam que os usuários executem apenas a
versão mais recente implantada de seu aplicativo Web, pois isso é normal dentro do
modelo de distribuição da Web tradicional. No entanto, um PWA offline-first é mais
semelhante a um aplicativo móvel nativo, em que os usuários não estão
necessariamente executando a versão mais recente.

Conforme explicado na seção Atualizações em segundo plano, depois que você


implanta uma atualização em seu aplicativo, cada usuário existente continua a usar
uma versão anterior para pelo menos mais uma visita porque a atualização ocorre em
segundo plano e não é ativada até que o usuário vá embora. Além disso, a versão
anterior que está sendo usada não é necessariamente a anterior que você implantou. A
versão anterior pode ser qualquer versão histórica, dependendo de quando o usuário
concluiu uma atualização pela última vez.

Isso pode ser um problema se as partes de front-end e back-end do aplicativo exigirem


um contrato sobre o esquema para solicitações de API. Você não deve implantar
alterações de esquema de API incompatíveis com versões anteriores até ter certeza de
que todos os usuários atualizaram. Como alternativa, impeça que os usuários usem
versões mais antigas incompatíveis do aplicativo. Esse requisito de cenário é o mesmo
que para aplicativos móveis nativos. Se você implantar uma alteração interruptiva nas
APIs do servidor, o aplicativo cliente será interrompido para usuários que ainda não
atualizaram.

Se possível, não implante alterações interruptivas em suas APIs de back-end. Se você


precisar fazer isso, considere usar APIs padrão do Service Worker, como
ServiceWorkerRegistration , para determinar se o aplicativo está atualizado e, caso
contrário, para impedir o uso.

Interferência com páginas renderizadas pelo servidor


Conforme descrito na seção Páginas renderizadas pelo servidor de suporte , se você
quiser ignorar o comportamento do trabalhador do serviço de retornar /index.html
conteúdo para todas as solicitações de navegação, edite a lógica em seu trabalho de
serviço.

Todo o conteúdo do manifesto do ativo do trabalho de


serviço é armazenado em cache por padrão
Conforme descrito na seção Cache de ativos de controle , o arquivo service-worker-
assets.js é gerado durante o build e lista todos os ativos que o trabalho de serviço

deve buscar e armazenar em cache.

Como essa lista por padrão inclui tudo o que é emitido para wwwroot , incluindo o
conteúdo fornecido por pacotes e projetos externos, você deve ter cuidado para não
colocar muito conteúdo lá. Se o wwwroot diretório contiver milhões de imagens, o
trabalhador do serviço tentará buscar e armazenar em cache todas elas, consumindo
largura de banda excessiva e provavelmente não concluindo com êxito.

Implemente a lógica arbitrária para controlar qual subconjunto do conteúdo do


manifesto deve ser buscado e armazenado em cache editando a onInstall função em
service-worker.published.js .
Interação com autenticação
O modelo do PWA pode ser usado em conjunto com a autenticação. Um PWA com
capacidade offline também pode dar suporte à autenticação quando o usuário tem
conectividade de rede inicial.

Quando um usuário não tem conectividade de rede, ele não pode autenticar ou obter
tokens de acesso. Por padrão, tentar visitar a página de logon sem acesso à rede resulta
em uma mensagem de "erro de rede". Você deve criar um fluxo de interface do usuário
que permita que o usuário execute tarefas úteis enquanto estiver offline sem tentar
autenticar o usuário ou obter tokens de acesso. Como alternativa, você pode projetar o
aplicativo para falhar normalmente quando a rede não estiver disponível. Se o aplicativo
não puder ser projetado para lidar com esses cenários, talvez você não queira habilitar o
suporte offline.

Quando um aplicativo projetado para uso online e offline estiver online novamente:

O aplicativo pode precisar provisionar um novo token de acesso.


O aplicativo deve detectar se um usuário diferente está conectado ao serviço para
que ele possa aplicar operações à conta do usuário que foram feitas enquanto eles
estavam offline.

Para criar um aplicativo PWA offline que interage com a autenticação:

Substitua o AccountClaimsPrincipalFactory<TAccount> por uma fábrica que


armazena o último usuário conectado e usa o usuário armazenado quando o
aplicativo está offline.
Operações de fila enquanto o aplicativo está offline e as aplica quando o aplicativo
retorna online.
Durante a saída, limpe o usuário armazenado.

O CarChecker aplicativo de exemplo demonstra as abordagens anteriores. Confira as


seguintes partes do aplicativo:

OfflineAccountClaimsPrincipalFactory
( Client/Data/OfflineAccountClaimsPrincipalFactory.cs )
LocalVehiclesStore ( Client/Data/LocalVehiclesStore.cs )

LoginStatus component ( Client/Shared/LoginStatus.razor )

Recursos adicionais
Solucionar problemas de integridade do script do PowerShell
SignalR negociação entre origens para autenticação
Hospedar e implantar o ASP.NET Core
Blazor
Artigo • 26/09/2022 • 23 minutos para o fim da leitura

Este artigo explica como hospedar e implantar aplicativos Blazor.

Publicar o aplicativo
Os aplicativos são publicados para implantação na configuração de versão.

7 Observação

Publique uma Blazor WebAssemblysolução hospedada do projeto Server .

Visual Studio

1. Selecione Versão>Publicar {APLICATIVO} na barra de navegação.


2. Selecione o botão destino de publicação. Para publicar localmente, selecione
Pasta.
3. Aceite o local padrão no campo Escolher uma pasta ou especifique um local
diferente. Selecione o botão Publish .

Publicar o aplicativo 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.

Locais de publicação:

Blazor WebAssembly
Autônomo: o aplicativo é publicado na pasta /bin/Release/{TARGET
FRAMEWORK}/publish/wwwroot . Para implantar o aplicativo como um site estático,

copie o conteúdo da pasta wwwroot para o host do site estático.


Hospedado: o aplicativo Blazor WebAssembly cliente é publicado na pasta
/bin/Release/{TARGET FRAMEWORK}/publish/wwwroot do aplicativo de servidor,

juntamente com qualquer outro ativo Web estático do aplicativo de servidor.


Implante o conteúdo da pasta publish no host.
Blazor Server: o aplicativo é publicado na pasta /bin/Release/{TARGET
FRAMEWORK}/publish . Implante o conteúdo da pasta publish no host.

Os ativos na pasta são implantados no servidor Web. A implantação pode ser um


processo manual ou automatizado, dependendo das ferramentas de desenvolvimento
em uso.

Pools de Aplicativos do IIS


Não há suporte para o compartilhamento de um pool de aplicativos entre aplicativos
ASP.NET Core, inclusive para aplicativos Blazor. Use um pool de aplicativos por
aplicativo ao hospedar com o IIS e evite o uso dos diretórios virtuais do IIS para
hospedar vários aplicativos.

Um ou mais aplicativos Blazor WebAssembly hospedados por um aplicativo ASP.NET


Core, conhecido como uma solução Blazor WebAssembly hospedada, têm suporte para
um pool de aplicativos. No entanto, não recomendamos nem damos suporte à
atribuição de um só pool de aplicativos a várias soluções Blazor WebAssembly
hospedadas ou em cenários de hospedagem de subaplicativos. Para obter mais
informações, consulte Configuração avançada.

Para obter mais informações sobre as soluções, consulte Ferramentas para ASP.NET Core
Blazor.

Caminho base do aplicativo


O caminho base do aplicativo é o caminho da URL raiz do aplicativo. Considere o
seguinte aplicativo ASP.NET Core e o subaplicativo Blazor:

O aplicativo ASP.NET Core é chamado MyApp :


O aplicativo reside fisicamente em d:/MyApp .
As solicitações são recebidas em https://www.contoso.com/{MYAPP RESOURCE} .
Um aplicativo Blazor chamado CoolApp é um subaplicativo de MyApp :
O subaplicativo reside fisicamente em d:/MyApp/CoolApp .
As solicitações são recebidas em https://www.contoso.com/CoolApp/{COOLAPP
RESOURCE} .

Sem especificar configuração adicional para CoolApp , o subaplicativo neste cenário não
tem conhecimento de onde ele reside no servidor. Por exemplo, o aplicativo não pode
construir URLs relativas corretas para seus recursos sem saber que ele reside no
caminho de URL relativa /CoolApp/ . Esse cenário também se aplica a vários cenários de
hospedagem e proxy reverso, quando um aplicativo não está hospedado em um
caminho de URL raiz.

Para fornecer a configuração do caminho base do aplicativo Blazor de


https://www.contoso.com/CoolApp/ , defina o caminho raiz relativo.

Ao configurar o caminho de URL relativa para um aplicativo, um componente que não


está no diretório raiz pode construir URLs em relação ao caminho raiz do aplicativo.
Componentes em níveis diferentes da estrutura de diretórios podem criar links para
outros recursos em locais em todo o aplicativo. O caminho base do aplicativo também é
usado para interceptar hiperlinks selecionados em que o destino href do link está
dentro do espaço do URI do caminho base do aplicativo. O roteador Blazor cuida da
navegação interna.

Em muitos cenários de hospedagem, o caminho da URL relativa para o aplicativo é a raiz


do aplicativo. Nesses casos padrão, o caminho base da URL relativa do aplicativo é o
seguinte:

Blazor WebAssembly: / configurado como <base href="/" /> em


wwwroot/index.html .

Blazor Server: ~/ configurado como <base href="~/" /> em


Pages/_Layout.cshtml .

Em outros cenários de hospedagem, como no GitHub Pages e em subaplicativos do IIS,


o caminho base do aplicativo precisa ser definido como o caminho de URL relativa do
servidor do aplicativo.

Blazor WebAssembly autônomo:

wwwroot/index.html :

HTML

<base href="/CoolApp/">

A barra à direita é obrigatória.

Blazor WebAssembly hospedado:

No projeto Client , wwwroot/index.html :

HTML

<base href="/CoolApp/">
A barra à direita é obrigatória.

No projeto Server , chame UsePathBaseno início do pipeline de processamento de


solicitação do aplicativo ( Program.cs ) para configurar o caminho base para
qualquer middleware seguinte que interaja com o caminho da solicitação:

C#

app.UsePathBase("/CoolApp");
// ...
app.UseRouting();

No aplicativo Blazor Server, use qualquer uma das seguintes abordagens:

Opção 1: use a marca <base> em Pages/_Layout.cshtml para definir o caminho


base do aplicativo:

HTML

<base href="/CoolApp/">

A barra à direita é obrigatória.

Opção 2: chame UsePathBaseno início do pipeline de processamento de


solicitação do aplicativo ( Program.cs ) para configurar o caminho base para
qualquer middleware seguinte que interaja com o caminho da solicitação:

C#

app.UsePathBase("/CoolApp");
// ...
app.UseRouting();

Chamar UsePathBase é recomendado quando você também deseja executar o


aplicativo Blazor Server localmente. Por exemplo, forneça a URL de inicialização
em Properties/launchSettings.json :

XML

"launchUrl": "https://localhost:{PORT}/CoolApp",

O espaço reservado {PORT} no exemplo anterior é a porta que corresponde à


porta segura no caminho de configuração applicationUrl . O seguinte exemplo
mostra o perfil de inicialização completo de um aplicativo na porta 7279:
XML

"BlazorSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7279;http://localhost:5279",
"launchUrl": "https://localhost:7279/CoolApp",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

7 Observação

Ao usar WebApplication (consulte Migrar do ASP.NET Core 5.0 para o 6.0),


app.UseRouting precisa ser chamado após UsePathBase para que o middleware de
roteamento possa observar o caminho modificado antes de fazer a
correspondência entre as rotas. Caso contrário, a correspondência das rotas é feita
antes que o caminho seja reescrito por UsePathBase , conforme descrito nos artigos
Ordenação de middleware e Roteamento.

Não prefixe links no aplicativo com uma barra "/". Evite o uso de um separador de
segmento de caminho ou use a notação de caminho relativo de barra de ponto ( ./ ):

❌ Incorreto: <a href="/account">


✔️Correto: <a href="account">
✔️Correto: <a href="./account">

Em solicitações à API Web Blazor WebAssembly com o serviço HttpClient, confirme se


os auxiliares JSON (HttpClientJsonExtensions) não prefixam URLs com uma barra "/" ( / ):

❌ Incorreto: var rsp = await client.GetFromJsonAsync("/api/Account");


✔️Correto: var rsp = await client.GetFromJsonAsync("api/Account");

Não prefixe links relativos do Gerenciador de Navegação com uma barra "/". Evite o uso
de um separador de segmento de caminho ou use a notação de caminho relativo de
barra de ponto ( ./ ):

❌ Incorreto: NavigationManager.NavigateTo("/other");
✔️Correto: NavigationManager.NavigateTo("other");
✔️Correto: NavigationManager.NavigateTo("./other");
Em configurações típicas para hospedagem do Azure/IIS, normalmente não é necessária
configuração adicional. Em alguns cenários de hospedagem não IIS e de hospedagem
de proxy reverso, configuração adicional do Middleware de Arquivo Estático pode ser
necessária para fornecer arquivos estáticos corretamente (por exemplo,
app.UseStaticFiles("/CoolApp"); ). A configuração necessária pode exigir definição

adicional para fornecer o script Blazor ( _framework/blazor.server.js ou


_framework/blazor.webassembly.js ). Saiba mais em Arquivos estáticos de Blazor no
ASP.NET Core.

Para um aplicativo Blazor WebAssembly com um caminho de URL relativa não raiz (por
exemplo, <base href="/CoolApp/"> ), o aplicativo não consegue localizar seus recursos
quando é executado localmente. Para superar esse problema durante o desenvolvimento
e os testes locais, você pode fornecer um argumento base de caminho que corresponde
ao valor de href da tag <base> no runtime. Não inclua uma barra à direita. Para passar
o argumento base do caminho ao executar o aplicativo localmente, execute o comando
dotnet run no diretório do aplicativo com a opção --pathbase :

CLI do .NET

dotnet run --pathbase=/{RELATIVE URL PATH (no trailing slash)}

Para um aplicativo Blazor WebAssembly com um caminho de URL relativa de /CoolApp/


( <base href="/CoolApp/"> ), o comando é:

CLI do .NET

dotnet run --pathbase=/CoolApp

Se preferir configurar o perfil de inicialização do aplicativo para especificar pathbase


automaticamente em vez de manualmente com dotnet run ,defina a propriedade
commandLineArgs em Properties/launchSettings.json . O seguinte também configura a

URL de inicialização ( launchUrl ):

JSON

"commandLineArgs": "--pathbase=/{RELATIVE URL PATH (no trailing slash)}",


"launchUrl": "{RELATIVE URL PATH (no trailing slash)}",

Usando CoolApp como exemplo:

JSON
"commandLineArgs": "--pathbase=/CoolApp",
"launchUrl": "CoolApp",

Usando dotnet run com a opção --pathbase ou uma configuração de perfil de


inicialização que define o caminho base, o aplicativo Blazor WebAssembly responde
localmente em http://localhost:port/CoolApp .

Blazor Server Configuração de


MapFallbackToPage
Em cenários em que um aplicativo requer uma área separada com recursos
personalizados e componentes Razor:

Crie uma pasta dentro da pasta Pages do aplicativo para manter os recursos. Por
exemplo, uma seção de administrador de um aplicativo é criada em uma nova
pasta chamada Admin ( Pages/Admin ).

Crie uma página raiz ( _Host.cshtml ) para a área. Por exemplo, crie um arquivo
Pages/Admin/_Host.cshtml na página raiz principal do aplicativo
( Pages/_Host.cshtml ). Não forneça uma diretiva @page na página _Host do
Administrador.

Adicione um layout à pasta da área (por exemplo, Pages/Admin/_Layout.razor ). No


layout da área separada, defina a marca <base> href para corresponder à pasta da
área (por exemplo, <base href="/Admin/" /> ). Para fins de demonstração, adicione
~/ aos recursos estáticos na página. Por exemplo:

~/css/bootstrap/bootstrap.min.css
~/css/site.css

~/BlazorSample.styles.css (o namespace do aplicativo de exemplo é

BlazorSample )
~/_framework/blazor.server.js (script Blazor)

Se a área deve ter a própria pasta de ativo estático, adicione a pasta e especifique
seu local para o Middleware de Arquivo Estático em Program.cs (por exemplo,
app.UseStaticFiles("/Admin/wwwroot") ).

Componentes Razor são adicionados à pasta da área. No mínimo, adicione um


componente Index à pasta da área com a diretiva @page correta para a área. Por
exemplo, adicione um arquivo Pages/Admin/Index.razor com base no arquivo
Pages/Index.razor padrão do aplicativo. Indique a área do Administrador como o

modelo de rota na parte superior do arquivo ( @page "/admin" ). Adicione


componentes adicionais conforme necessário. Por exemplo,
Pages/Admin/Component1.razor com uma diretiva @page e um modelo de rota de
@page "/admin/component1 .

Em Program.cs , chame MapFallbackToPage para o caminho de solicitação da área


imediatamente antes do caminho da página raiz de fallback para a página _Host :

C#

...
app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("~/Admin/{*clientroutes:nonfile}",
"/Admin/_Host");
app.MapFallbackToPage("/_Host");

app.Run();

Hospedar vários aplicativos Blazor


WebAssembly
Para obter mais informações sobre como hospedar vários aplicativos Blazor
WebAssembly em uma Blazorsolução hospedada, consulte Vários aplicativos Blazor
WebAssembly hospedados do ASP.NET Core.

Implantação
Confira orientações de implantação nos tópicos a seguir:

Hospedar e implantar o ASP.NET Core Blazor WebAssembly


Hospedar e implantar o ASP.NET Core Blazor Server
Hospedar e implantar Blazor Server
Artigo • 03/12/2022 • 62 minutos para o fim da leitura

Este artigo explica como hospedar e implantar um Blazor Server aplicativo usando
ASP.NET Core.

Valores de configuração do host


Blazor Server os aplicativos podem aceitar valores de configuração de host genérico.

Implantação
O uso do Blazor Server modeloBlazor de hospedagem é executado no servidor de
dentro de um aplicativo ASP.NET Core. Atualizações de interface do usuário,
manipulação de eventos e chamadas JavaScript são tratadas em uma SignalR conexão.

É necessário um servidor Web capaz de hospedar um aplicativo ASP.NET Core. O Visual


Studio inclui oBlazor Server modelo de projeto de aplicativo ( blazorserver modelo ao
usar o dotnet new comando). Para obter mais informações sobre Blazor modelos de
projeto, consulte ASP.NET Core Blazor estrutura do projeto.

Escalabilidade
Ao considerar a escalabilidade de um único servidor (escalar verticalmente), a memória
disponível para um aplicativo provavelmente é o primeiro recurso que o aplicativo
esgota à medida que as demandas do usuário aumentam. A memória disponível no
servidor afeta:

Número de circuitos ativos aos quais um servidor pode dar suporte.


Latência de interface do usuário no cliente.

Para obter diretrizes sobre como criar aplicativos de servidor seguros e


escalonáveisBlazor, consulte Diretrizes de mitigação de ameaças para ASP.NET Core
Blazor Server.

Cada circuito usa aproximadamente 250 KB de memória para um aplicativo mínimo de


estilo Olá, Mundo. O tamanho de um circuito depende do código do aplicativo e dos
requisitos de manutenção de estado associados a cada componente. Recomendamos
que você meça as demandas de recursos durante o desenvolvimento para seu aplicativo
e infraestrutura, mas a linha de base a seguir pode ser um ponto de partida no
planejamento de seu destino de implantação: se você espera que seu aplicativo dê
suporte a 5.000 usuários simultâneos, considere o orçamento de pelo menos 1,3 GB de
memória do servidor para o aplicativo (ou ~273 KB por usuário).

Configuração de SignalR
SignalRAs condições de hospedagem e dimensionamento se aplicam a Blazor
aplicativos que usam SignalR.

Transportes
Blazor funciona melhor ao usar WebSockets como transporte SignalR devido à menor
latência, melhor confiabilidade e segurança aprimorada. A Sondagem Longa é usada
por SignalR quando WebSockets não está disponível ou quando o aplicativo está
explicitamente configurado para usar a Sondagem Longa. Ao implantar em Serviço de
Aplicativo do Azure, configure o aplicativo para usar WebSockets nas configurações de
portal do Azure para o serviço. Para obter detalhes sobre como configurar o aplicativo
para Serviço de Aplicativo do Azure, consulte as SignalR diretrizes de publicação.

Um aviso do console será exibido se a Sondagem Longa for utilizada:

Falha ao se conectar por meio de WebSockets, usando o transporte de fallback de


Sondagem Longa. Isso pode ser devido a uma VPN ou proxy bloqueando a
conexão.

Falhas globais de implantação e conexão


Recomendações para implantações globais em data centers geográficos:

Implante o aplicativo nas regiões onde a maioria dos usuários reside.


Leve em consideração o aumento da latência para o tráfego em todos os
continentes.
Para hospedagem do Azure, use o Serviço do AzureSignalR.

Se um aplicativo implantado frequentemente exibir a interface do usuário de reconexão


devido a tempos limite de ping causados pela latência da Internet, alonge os tempos
limite do servidor e do cliente:

Servidor

Pelo menos o dobro do tempo máximo de ida e volta esperado entre o cliente e o
servidor. Teste, monitore e revise os tempos limite conforme necessário. Para o
SignalR hub, defina o ClientTimeoutInterval (padrão: 30 segundos) e
HandshakeTimeout (padrão: 15 segundos). O exemplo a seguir pressupõe que
KeepAliveInterval usa o valor padrão de 15 segundos.

) Importante

O KeepAliveInterval não está diretamente relacionado à interface do usuário


de reconexão que aparece. O intervalo de Keep-Alive não precisa
necessariamente ser alterado. Se o problema de aparência da interface do
usuário de reconexão for devido a tempos limite, o ClientTimeoutInterval e
HandshakeTimeout poderá ser aumentado e o intervalo de Keep-Alive
poderá permanecer o mesmo. A consideração importante é que, se você
alterar o intervalo de Keep-Alive, verifique se o valor de tempo limite do
cliente é pelo menos o dobro do valor do intervalo de Keep-Alive e se o
intervalo de Keep-Alive no cliente corresponde à configuração do servidor.

No exemplo a seguir, o ClientTimeoutInterval é aumentado para 60


segundos e o HandshakeTimeout é aumentado para 30 segundos.

Para um Blazor Server aplicativo em Program.cs :

C#

builder.Services.AddServerSideBlazor()
.AddHubOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Para obter mais informações, consulte ASP.NET Core BlazorSignalR diretrizes.

Cliente

Normalmente, o dobro do valor usado para o do KeepAliveInterval servidor definir


o tempo limite para o tempo limite do servidor do cliente
( serverTimeoutInMilliseconds ou ServerTimeout, padrão: 30 segundos).

) Importante

O intervalo de Keep-Alive ( keepAliveIntervalInMilliseconds ou


KeepAliveInterval) não está diretamente relacionado à interface do usuário
de reconexão que aparece. O intervalo de Keep-Alive não precisa
necessariamente ser alterado. Se o problema de aparência da interface do
usuário de reconexão for devido a tempos limite, o tempo limite do servidor
poderá ser aumentado e o intervalo de Keep-Alive poderá permanecer o
mesmo. A consideração importante é que, se você alterar o intervalo de Keep-
Alive, verifique se o valor de tempo limite é pelo menos o dobro do valor do
intervalo de Keep-Alive e se o intervalo de Keep-Alive no servidor
corresponde à configuração do cliente.

No exemplo a seguir, um valor personalizado de 60 segundos é usado para o


tempo limite do servidor.

Em Pages/_Host.cshtml um Blazor Server aplicativo:

HTML

<script src="_framework/blazor.server.js" autostart="false"></script>


<script>
Blazor.start({
configureSignalR: function (builder) {
let c = builder.build();
c.serverTimeoutInMilliseconds = 60000;
builder.build = () => {
return c;
};
}
});
</script>

Ao criar uma conexão de hub em um componente, defina o ServerTimeout


(padrão: 30 segundos) e HandshakeTimeout (padrão: 15 segundos) no compilado
HubConnection.

O exemplo a seguir baseia-se no Index componente no SignalR tutorial com


Blazor. O tempo limite do servidor é aumentado para 60 segundos e o tempo
limite do handshake é aumentado para 30 segundos:

C#

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();

hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);
hubConnection.On<string, string>("ReceiveMessage", (user, message)
=> ...

await hubConnection.StartAsync();
}

Ao alterar os valores do tempo limite do servidor (ServerTimeout) ou do intervalo


de Keep-Alive (KeepAliveInterval:
O tempo limite do servidor deve ser pelo menos o dobro do valor atribuído ao
intervalo de Keep-Alive.
O intervalo de Keep-Alive deve ser menor ou igual a metade do valor atribuído
ao tempo limite do servidor.

Para obter mais informações, consulte ASP.NET Core BlazorSignalR diretrizes.

Serviço do Azure SignalR


É recomendável usar o Serviço do Azure SignalR para Blazor Server aplicativos. O serviço
funciona em conjunto com o Hub do Blazor aplicativo para escalar verticalmente um
Blazor Server aplicativo para um grande número de conexões simultâneas SignalR .
Além disso, o alcance global do Serviço e os SignalR data centers de alto desempenho
ajudam significativamente na redução da latência devido à geografia.

) Importante

Quando WebSockets são desabilitados, Serviço de Aplicativo do Azure simula


uma conexão em tempo real usando a Sondagem Longa HTTP. A Sondagem Longa
HTTP é visivelmente mais lenta do que a execução com WebSockets habilitados, o
que não usa sondagem para simular uma conexão cliente-servidor. Caso a
Sondagem Longa precise ser usada, talvez seja necessário configurar o intervalo
máximo de sondagem ( MaxPollIntervalInSeconds ), que define o intervalo máximo
de sondagem permitido para conexões de Sondagem Longa no Serviço do Azure
SignalR se o serviço retornar de WebSockets para Sondagem Longa. Se a próxima
solicitação de sondagem não entrar no MaxPollIntervalInSeconds , o Serviço do
Azure SignalR limpará a conexão do cliente. Observe que o Serviço do Azure
SignalR também limpa as conexões quando armazenadas em cache aguardando
para gravar o tamanho do buffer é maior que 1 MB para garantir o desempenho do
serviço. O valor padrão para MaxPollIntervalInSeconds é 5 segundos. A
configuração é limitada a 1 a 300 segundos.

É recomendável usar WebSockets para Blazor Server aplicativos implantados em


Serviço de Aplicativo do Azure. O Serviço do Azure SignalR usa WebSockets por
padrão. Se o aplicativo não usar o Serviço do AzureSignalR, consulte Publicar um
aplicativo ASP.NET Core SignalR para Serviço de Aplicativo do Azure.

Para obter mais informações, consulte:

O que é o Serviço do Azure SignalR ?


Guia de desempenho do Serviço do Azure SignalR
Publicar um aplicativo de ASP.NET Core SignalR para Serviço de Aplicativo
do Azure

Configuração
Para configurar um aplicativo para o Serviço do Azure SignalR , o aplicativo deve dar
suporte a sessões autoadesivas, em que os clientes são redirecionados de volta para o
mesmo servidor durante a pré-geração. A ServerStickyMode opção ou o valor de
configuração é definido como Required . Normalmente, um aplicativo cria a
configuração usando uma das seguintes abordagens:

Program.cs :

C#

builder.Services.AddSignalR().AddAzureSignalR(options =>
{
options.ServerStickyMode =
Microsoft.Azure.SignalR.ServerStickyMode.Required;
});

Configuração (use uma das seguintes abordagens):

Em appsettings.json :

JSON

"Azure:SignalR:StickyServerMode": "Required"

Asconfigurações do Aplicativo de Configuração> do serviço de aplicativo no


portal do Azure (Nome: Azure__SignalR__StickyServerMode , Valor: Required ).
Essa abordagem será adotada para o aplicativo automaticamente se você
provisionar o Serviço do AzureSignalR.

7 Observação
O seguinte erro é gerado por um aplicativo que não habilitou sessões autoadesivas
para o Serviço do Azure SignalR :

blazor.server.js:1 Erro não iniciado (em promessa): invocação cancelada devido


ao fechamento da conexão subjacente.

Provisionar o Serviço do Azure SignalR


Para provisionar o Serviço do Azure SignalR para um aplicativo no Visual Studio:

1. Crie um perfil de publicação dos Aplicativos do Azure no Visual Studio para o


Blazor Server aplicativo.
2. Adicione a dependência do Serviço do Azure SignalR ao perfil. Se a assinatura do
Azure não tiver uma instância do Serviço do Azure SignalR pré-existente para
atribuir ao aplicativo, selecione Criar uma nova instância do Serviço do Azure
SignalR para provisionar uma nova instância de serviço.
3. Publicar o aplicativo no Azure.

O provisionamento do Serviço do Azure SignalR no Visual Studio habilita


automaticamente as sessões autoadesivas e adiciona a SignalR cadeia de conexão à
configuração do serviço de aplicativo.

Escalabilidade nos Aplicativos de Contêiner do Azure


O dimensionamento de Blazor Server aplicativos nos Aplicativos de Contêiner do Azure
requer considerações específicas, além de usar o Serviço do AzureSignalR. Devido à
maneira como o roteamento de solicitações é tratado, o serviço de proteção de dados
ASP.NET Core deve ser configurado para manter chaves em um local centralizado que
todas as instâncias de contêiner podem acessar. As chaves podem ser armazenadas em
Armazenamento de Blobs do Azure e protegidas com Key Vault do Azure. O serviço de
proteção de dados usa as chaves para desserializar Razor componentes.

7 Observação

Para obter uma exploração mais profunda desse cenário e dimensionamento de


aplicativos de contêiner, consulte Dimensionamento de aplicativos de ASP.NET
Core no Azure. O tutorial explica como criar e integrar os serviços necessários para
hospedar aplicativos nos Aplicativos de Contêiner do Azure. As etapas básicas
também são fornecidas nesta seção.
1. Para configurar o serviço de proteção de dados para usar Armazenamento de
Blobs do Azure e Key Vault do Azure, faça referência aos seguintes pacotes NuGet:

Azure.Identity : fornece classes para trabalhar com os serviços de


gerenciamento de identidade e acesso do Azure.
Microsoft.Extensions.Azure : fornece métodos de extensão úteis para
executar as principais configurações do Azure.
Azure.Extensions.AspNetCore.DataProtection.Blobs : permite armazenar
ASP.NET Core chaves de Proteção de Dados em Armazenamento de Blobs do
Azure para que as chaves possam ser compartilhadas em várias instâncias de
um aplicativo Web.
Azure.Extensions.AspNetCore.DataProtection.Keys : habilita a proteção de
chaves em repouso usando o recurso Azure Key Vault Key
Encryption/Wrapping.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET,


consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de
consumo de pacotes (documentação do NuGet). Confirme as versões
corretas de pacote em NuGet.org .

2. Atualize Program.cs com o seguinte código realçado:

C#

using Azure.Identity;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Azure;
var builder = WebApplication.CreateBuilder(args);
var BlobStorageUri = builder.Configuration["AzureURIs:BlobStorage"];
var KeyVaultURI = builder.Configuration["AzureURIs:KeyVault"];

builder.Services.AddRazorPages();
builder.Services.AddHttpClient();
builder.Services.AddServerSideBlazor();

builder.Services.AddAzureClientsCore();

builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri(BlobStorageUri),
new
DefaultAzureCredential())
.ProtectKeysWithAzureKeyVault(new Uri(KeyVaultURI),
new
DefaultAzureCredential());
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

As alterações anteriores permitem que o aplicativo gerencie a proteção de dados


usando uma arquitetura centralizada e escalonável. DefaultAzureCredential
descobre a identidade gerenciada do aplicativo de contêiner depois que o código
é implantado no Azure e o usa para se conectar ao armazenamento de blobs e ao
cofre de chaves do aplicativo.

3. Para criar a identidade gerenciada do aplicativo de contêiner e conceder-lhe


acesso ao armazenamento de blobs e a um cofre de chaves, conclua as seguintes
etapas:
a. No Portal do Azure, navegue até a página de visão geral do aplicativo de
contêiner.
b. Selecione Conector de Serviço na navegação à esquerda.
c. Selecione + Criar na navegação superior.
d. No menu Criar submenu de conexão , insira os seguintes valores:

Contêiner: selecione o aplicativo de contêiner que você criou para


hospedar seu Blazor Server aplicativo.
Tipo de serviço: selecione Armazenamento de Blobs.
Assinatura: selecione a assinatura que possui o aplicativo de contêiner.
Nome da conexão: insira um nome de scalablerazorstorage .
Tipo de cliente: selecione .NET e, em seguida, selecione Avançar.

e. Selecione Identidade gerenciada atribuída pelo sistema e selecione Avançar.


f. Use as configurações de rede padrão e selecione Avançar.
g. Depois que o Azure validar as configurações, selecione Criar.

Repita as configurações anteriores para o cofre de chaves. Selecione o serviço e a


chave apropriados do cofre de chaves na guia Noções básicas .
Serviço de aplicativo do Azure
Esta seção só se aplica a aplicativos que não usam o Serviço do AzureSignalR.

Quando o Serviço do Azure SignalRnão é usado, o Serviço de Aplicativo requer


configuração para afinidade de ARR (Roteamento de Solicitação de Aplicativo) e
WebSockets. Os clientes conectam seus WebSockets diretamente ao aplicativo, não ao
Serviço do Azure SignalR .

Use as diretrizes a seguir para configurar o aplicativo:

Configure o aplicativo no Serviço de Aplicativo do Azure.


Serviço de Aplicativo Limites do Plano.

IIS
Ao usar o IIS, habilite:

WebSockets no IIS.
Sessões autoadesivas com Roteamento de Solicitação de Aplicativo.

Kubernetes
Crie uma definição de entrada com as seguintes anotações do Kubernetes para sessões
autoadesivas :

YAML

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: <ingress-name>
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "affinity"
nginx.ingress.kubernetes.io/session-cookie-expires: "14400"
nginx.ingress.kubernetes.io/session-cookie-max-age: "14400"

Linux com o Nginx


Siga as diretrizes para um aplicativo ASP.NET Core SignalR com as seguintes alterações:
Altere o location caminho de /hubroute ( location /hubroute { ... } ) para o
caminho / raiz ( location / { ... } ).
Remova a configuração para buffer de proxy ( proxy_buffering off; ) porque a
configuração se aplica apenas a SSE (Eventos Enviados pelo Servidor), que não
são relevantes para Blazor interações cliente-servidor do aplicativo.

Para obter mais informações e diretrizes de configuração, consulte os seguintes


recursos:

SignalR ASP.NET Core hospedagem e dimensionamento de produção


Host ASP.NET Core no Linux com Nginx
Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores
de carga
NGINX como um proxy WebSocket
Proxy do WebSocket
Consulte desenvolvedores em fóruns de suporte que não são da Microsoft:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Linux com o Apache


Para hospedar um Blazor aplicativo por trás do Apache no Linux, configure ProxyPass
para tráfego HTTP e WebSockets.

No exemplo a seguir:

Kestrel o servidor está em execução no computador host.


O aplicativo escuta o tráfego na porta 5000.

ProxyRequests On
ProxyPreserveHost On
ProxyPassMatch ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass /_blazor ws://localhost:5000/_blazor
ProxyPass / http://localhost:5000/
ProxyPassReverse / http://localhost:5000/

Habilite os seguintes módulos:


a2enmod proxy
a2enmod proxy_wstunnel

Verifique se há erros de WebSockets no console do navegador. Exemplo de erros:

O Firefox não pode estabelecer uma conexão com o servidor em ws://the-domain-


name.tld/_blazor?id=XXX
Erro: falha ao iniciar o transporte 'WebSockets': erro: ocorreu um erro com o
transporte.
Erro: Falha ao iniciar o transporte 'LongPolling': TypeError: this.transport é
indefinido
Erro: não é possível se conectar ao servidor com qualquer um dos transportes
disponíveis. Falha de WebSockets
Erro: não é possível enviar dados se a conexão não estiver no estado 'Conectado'.

Para obter mais informações e diretrizes de configuração, consulte os seguintes


recursos:

Hospedar o ASP.NET Core no Linux com o Apache


Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores
de carga
Documentação do Apache
Consulte desenvolvedores em fóruns de suporte que não são da Microsoft:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Medir a latência de rede


JS A interoperabilidade pode ser usada para medir a latência de rede, como demonstra
o exemplo a seguir.

Shared/MeasureLatency.razor :

razor

@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)


{
<span>Calculating...</span>
}
else
{
<span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
private DateTime startTime;
private TimeSpan? latency;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
startTime = DateTime.UtcNow;
var _ = await JS.InvokeAsync<string>("toString");
latency = DateTime.UtcNow - startTime;
StateHasChanged();
}
}
}

Para uma experiência razoável de interface do usuário, recomendamos uma latência de


interface do usuário sustentada de 250 ms ou menos.

Blazor Server modelo de memória


Blazor Server cria um novo circuito por sessão de usuário. Cada sessão de usuário
corresponde à renderização de um único documento no navegador. Por exemplo, várias
guias criam várias sessões.

Blazor Server mantém uma conexão constante com o navegador, chamado de circuito,
que iniciou a sessão. As conexões podem ser perdidas a qualquer momento por vários
motivos, como quando o usuário perde a conectividade de rede ou fecha abruptamente
o navegador. Quando uma conexão é perdida, Blazor tem um mecanismo de
recuperação que coloca um número limitado de circuitos em um pool "desconectado",
dando aos clientes uma quantidade limitada de tempo para reconectar e restabelecer a
sessão (padrão: 3 minutos).

Depois desse tempo, Blazor libera o circuito e descarta a sessão. Desse ponto em diante,
o circuito é qualificado para GC (coleta de lixo) e é reivindicado quando uma coleta para
a geração de GC do circuito é disparada. Um aspecto importante a ser compreendido é
que os circuitos têm um longo tempo de vida, o que significa que a maioria dos objetos
com raiz pelo circuito eventualmente chega à Gen 2. Como resultado, talvez você não
veja esses objetos liberados até que ocorra uma coleção Gen 2.
Medir o uso de memória em geral
Pré-requisitos:

O aplicativo deve ser publicado na Configuração de versão . As medidas de


configuração de depuração não são relevantes, pois o código gerado não
representa o código usado para uma implantação de produção.
O aplicativo deve ser executado sem um depurador anexado, pois isso também
pode afetar o comportamento do aplicativo e estragar os resultados. No Visual
Studio, inicie o aplicativo sem depuração selecionando Depurar>Iniciar Sem
Depuração na barra de menus ou Ctrl + F5 usando o teclado.
Considere os diferentes tipos de memória para entender quanta memória é
realmente usada pelo .NET. Em geral, os desenvolvedores inspecionam o uso de
memória do aplicativo no Gerenciador de Tarefas no sistema operacional
Windows, que normalmente oferece um limite superior da memória real em uso.
Para obter mais informações, consulte os seguintes artigos:
Análise de desempenho de memória do .NET : em particular, consulte a seção
em Conceitos básicos de memória .
Fluxo de trabalho do diagnóstico de problemas de desempenho de memória
(série de três partes) : os links para os três artigos da série estão na parte
superior de cada artigo da série.

Uso de memória aplicado a Blazor


Calculamos a memória usada pelo blazor da seguinte maneira:

(Circuitos ativos × memória por circuito) + (Circuitos desconectados × memória por


circuito)

A quantidade de memória que um circuito usa e os circuitos ativos potenciais máximos


que um aplicativo pode manter dependem em grande parte de como o aplicativo é
gravado. O número máximo de circuitos ativos possíveis é descrito aproximadamente
por:

Memória / máxima disponível Memória = por circuito Máximo de circuitos ativos


potenciais

Para que ocorra um vazamento de memória no Blazor, o seguinte deve ser verdadeiro:

A memória deve ser alocada pela estrutura, não pelo aplicativo. Se você alocar
uma matriz de 1 GB no aplicativo, o aplicativo deverá gerenciar o descarte da
matriz.
A memória não deve ser usada ativamente, o que significa que o circuito não está
ativo e foi removido do cache de circuitos desconectados. Se você tiver o máximo
de circuitos ativos em execução, ficar sem memória será um problema de escala,
não um vazamento de memória.
Uma GC (coleta de lixo) para a geração de GC do circuito foi executada, mas o
coletor de lixo não conseguiu reivindicar o circuito porque outro objeto na
estrutura está mantendo uma forte referência ao circuito.

Em outros casos, não há perda de memória. Se o circuito estiver ativo (conectado ou


desconectado), o circuito ainda estará em uso.

Se uma coleção para a geração de GC do circuito não for executada, a memória não
será liberada porque o coletor de lixo não precisará liberar a memória nesse momento.

Se uma coleção de uma geração de GC for executada e liberar o circuito, você deverá
validar a memória em relação às estatísticas do GC, não ao processo, pois o .NET pode
decidir manter a memória virtual ativa.

Se a memória não for liberada, você deverá encontrar um circuito que não esteja ativo
ou desconectado e com raiz por outro objeto na estrutura. Em qualquer outro caso, a
incapacidade de liberar memória é um problema de aplicativo no código do
desenvolvedor.

Reduzir o uso de memória


Adote qualquer uma das seguintes estratégias para reduzir o uso de memória de um
aplicativo:

Limite a quantidade total de memória usada pelo processo .NET. Para obter mais
informações, consulte Opções de configuração de runtime para coleta de lixo.
Reduza o número de circuitos desconectados.
Reduza o tempo em que um circuito tem permissão para estar no estado
desconectado.
Dispare uma coleta de lixo manualmente para executar uma coleta durante
períodos de tempo de inatividade.
Configure a coleta de lixo no modo estação de trabalho, que dispara
agressivamente a coleta de lixo, em vez do modo servidor.

Ações adicionais
Capture um despejo de memória do processo quando as demandas de memória
forem altas e identificar se os objetos estão tirando mais memória e onde esses
objetos estão com raiz (o que contém uma referência a eles).
O .NET no modo servidor não libera a memória para o sistema operacional
imediatamente, a menos que ele precise fazer isso. Para obter mais informações
sobre as configurações do arquivo de projeto ( .csproj ) para controlar esse
comportamento, consulte Opções de configuração de runtime para coleta de lixo.
O GC do servidor pressupõe que seu aplicativo seja o único em execução no
sistema e possa usar todos os recursos do sistema. Se o sistema tiver 50 GB, o
coletor de lixo buscará usar os 50 GB completos de memória disponível antes de
disparar uma coleção Gen 2.

Para obter informações sobre a configuração de retenção de circuito desconectada,


consulte ASP.NET Core BlazorSignalR diretrizes.
Hospedar e implantar o ASP.NET Core
Blazor WebAssembly
Artigo • 01/12/2022 • 152 minutos para o fim da leitura

Este artigo explica como hospedar e implantar Blazor WebAssembly usando ASP.NET
Core, REDES de Distribuição de Conteúdo (CDN), servidores de arquivos e GitHub Pages.

Com o Blazor WebAssembly modelo de hospedagem:

O Blazor aplicativo, suas dependências e o runtime do .NET são baixados para o


navegador em paralelo.
O aplicativo é executado diretamente no thread da interface do usuário do
navegador.

Há suporte para as seguintes estratégias de implantação:

O Blazor aplicativo é atendido por um aplicativo ASP.NET Core. Esta estratégia é


abordada na seção Implantação hospedada com o ASP.NET Core.
O Blazor aplicativo é colocado em um servidor Web ou serviço de hospedagem
estático, em que o .NET não é usado para atender ao Blazor aplicativo. Essa
estratégia é abordada na seção Implantação autônoma , que inclui informações
sobre como hospedar um Blazor WebAssembly aplicativo como um subapa do IIS.
Um aplicativo ASP.NET Core hospeda vários Blazor WebAssembly aplicativos. Para
obter mais informações, consulte Vários aplicativos de ASP.NET Core Blazor
WebAssembly hospedados.

Compilação AOT (antecipada)


Blazor WebAssembly dá suporte à compilação AOT (antecipada), na qual você pode
compilar seu código .NET diretamente no WebAssembly. A compilação AOT resulta em
melhorias de desempenho de runtime em detrimento de um tamanho de aplicativo
maior.

Sem habilitar a compilação AOT, Blazor WebAssembly os aplicativos são executados no


navegador usando um interpretador de IL (Linguagem Intermediária do .NET)
implementado no WebAssembly. Como o código .NET é interpretado, os aplicativos
normalmente são executados mais lentamente do que em um runtime just-in-time (JIT)
do lado do servidor. A compilação AOT resolve esse problema de desempenho
compilando o código .NET de um aplicativo diretamente no WebAssembly para
execução nativa do WebAssembly pelo navegador. A melhoria de desempenho do AOT
pode gerar melhorias dramáticas para aplicativos que executam tarefas com uso
intensivo de CPU. A desvantagem de usar a compilação AOT é que os aplicativos
compilados por AOT geralmente são maiores do que seus equivalentes interpretados
por IL, portanto, geralmente levam mais tempo para serem baixados no cliente quando
solicitados pela primeira vez.

Para obter diretrizes sobre como instalar as ferramentas de build do .NET WebAssembly,
consulte Ferramentas para ASP.NET Core Blazor.

Para habilitar a compilação AOT do WebAssembly, adicione a <RunAOTCompilation>


propriedade definida como true ao Blazor WebAssembly arquivo de projeto do
aplicativo:

XML

<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>

Para compilar o aplicativo no WebAssembly, publique o aplicativo. A publicação da


Release configuração garante que a vinculação do .NET Intermediate Language (IL)

também seja executada para reduzir o tamanho do aplicativo publicado:

CLI do .NET

dotnet publish -c Release

A compilação AOT do WebAssembly só é executada quando o projeto é publicado. A


compilação AOT não é usada quando o projeto é executado durante o desenvolvimento
( Development ambiente) porque a compilação AOT geralmente leva vários minutos em
projetos pequenos e potencialmente muito mais tempo para projetos maiores. A
redução do tempo de compilação para a compilação AOT está em desenvolvimento
para versões futuras de ASP.NET Core.

O tamanho de um aplicativo compilado por Blazor WebAssembly AOT geralmente é


maior que o tamanho do aplicativo se compilado no .NET IL:

Embora a diferença de tamanho dependa do aplicativo, a maioria dos aplicativos


compilados por AOT tem cerca de duas vezes o tamanho de suas versões
compiladas por IL. Isso significa que o uso da compilação AOT compensa o
desempenho de tempo de carga para o desempenho do runtime. Se essa
compensação vale a pena usar a compilação AOT depende do seu aplicativo.
Blazor WebAssembly os aplicativos que têm uso intensivo de CPU geralmente se
beneficiam mais da compilação AOT.

O tamanho maior de um aplicativo compilado por AOT é devido a duas condições:


Mais código é necessário para representar instruções il .NET de alto nível no
WebAssembly nativo.
O AOT não corta DLLs gerenciadas quando o aplicativo é publicado. Blazor
requer as DLLs para metadados de reflexão e para dar suporte a determinados
recursos de runtime do .NET. Exigir as DLLs no cliente aumenta o tamanho do
download, mas fornece uma experiência .NET mais compatível.

7 Observação

Para propriedades e destinos do MSBuild Mono/WebAssembly, consulte


WasmApp.targets (repositório GitHub dotnet/runtime) . A documentação oficial
para propriedades comuns do MSBuild é planejada de acordo com as opções de
configuração do documento blazor msbuild (dotnet/docs #27395) .

Revinculação de runtime
Uma das maiores partes de um Blazor WebAssembly aplicativo é o runtime do .NET
baseado em WebAssembly ( dotnet.wasm ) que o navegador deve baixar quando o
aplicativo é acessado pela primeira vez pelo navegador de um usuário. A revinculação
do runtime do .NET WebAssembly corta o código de runtime não utilizado e, portanto,
melhora a velocidade de download.

A revinculação de runtime requer a instalação das ferramentas de build do .NET


WebAssembly. Para obter mais informações, consulte Ferramentas para ASP.NET Core
Blazor.

Com as ferramentas de build do .NET WebAssembly instaladas, a revinculação de


runtime é executada automaticamente quando um aplicativo é publicado na Release
configuração. A redução de tamanho é particularmente dramática ao desabilitar a
globalização. Para obter mais informações, consulte ASP.NET Core Blazor globalização e
localização.

Personalizar como os recursos de inicialização


são carregados
Personalize como os recursos de inicialização são carregados usando a
loadBootResource API. Para obter mais informações, consulte ASP.NET Core Blazor
inicialização.

Compactação
Quando um Blazor WebAssembly aplicativo é publicado, a saída é compactada
estaticamente durante a publicação para reduzir o tamanho do aplicativo e remover a
sobrecarga para compactação de runtime. Os seguintes algoritmos de compactação são
usados:

Brotli (nível mais alto)


Gzip

Blazor depende do host para servir os arquivos compactados apropriados. Ao usar um


projeto hospedado ASP.NET Core, o projeto host é capaz de executar a negociação de
conteúdo e fornecer os arquivos compactados estaticamente. Ao hospedar um Blazor
WebAssembly aplicativo autônomo, um trabalho adicional pode ser necessário para
garantir que arquivos compactados estaticamente sejam atendidos:

Para configuração de compactação do IIS web.config , consulte a seção


Compactação IIS: Brotli e Gzip .

Ao hospedar em soluções de hospedagem estáticas que não dão suporte à


negociação de conteúdo de arquivo compactado estaticamente, como o GitHub
Pages, considere configurar o aplicativo para buscar e decodificar arquivos
compactados brotli:

Obtenha o decodificador Do JavaScript Brotli do repositório GitHub


google/brotli . O arquivo de decodificador minificado é nomeado
decode.min.js e encontrado na pasta do js repositório.

7 Observação

Se a versão minificada do decode.js script ( decode.min.js ) falhar, tente


usar a versão não modificada ( decode.js ) em vez disso.

Atualize o aplicativo para usar o decodificador.

wwwroot/index.html No arquivo, defina autostart como false na Blazormarca


's <script> :
HTML

<script src="_framework/blazor.webassembly.js" autostart="false">


</script>

Após Blazora marca e antes da <script> marca de fechamento </body> ,


adicione o seguinte bloco de código <script> JavaScript:

HTML

<script type="module">
import { BrotliDecode } from './decode.min.js';
Blazor.start({
loadBootResource: function (type, name, defaultUri, integrity) {
if (type !== 'dotnetjs' && location.hostname !== 'localhost') {
return (async function () {
const response = await fetch(defaultUri + '.br', { cache:
'no-cache' });
if (!response.ok) {
throw new Error(response.statusText);
}
const originalResponseBuffer = await
response.arrayBuffer();
const originalResponseArray = new
Int8Array(originalResponseBuffer);
const decompressedResponseArray =
BrotliDecode(originalResponseArray);
const contentType = type ===
'dotnetwasm' ? 'application/wasm' : 'application/octet-
stream';
return new Response(decompressedResponseArray,
{ headers: { 'content-type': contentType } });
})();
}
}
});
</script>

Para obter mais informações sobre como carregar recursos de inicialização,


consulte ASP.NET Core Blazor inicialização.

Para desabilitar a compactação, adicione a BlazorEnableCompression propriedade


MSBuild ao arquivo de projeto do aplicativo e defina o valor como false :

XML

<PropertyGroup>
<BlazorEnableCompression>false</BlazorEnableCompression>
</PropertyGroup>
A BlazorEnableCompression propriedade pode ser passada para o dotnet publish
comando com a seguinte sintaxe em um shell de comando:

CLI do .NET

dotnet publish -p:BlazorEnableCompression=false

Reescrever as URLs para obter o roteamento


correto
O roteamento de solicitações para componentes de página em um Blazor WebAssembly
aplicativo não é tão simples quanto as solicitações de roteamento em um Blazor
Serveraplicativo hospedado . Considere um Blazor WebAssembly aplicativo com dois
componentes:

Main.razor : carrega na raiz do aplicativo e contém um link para o About


componente ( href="About" ).
About.razor : About componente.

Quando o documento padrão do aplicativo é solicitado usando a barra de endereços do


navegador (por exemplo, https://www.contoso.com/ ):

1. O navegador faz uma solicitação.


2. A página padrão é retornada, que geralmente index.html é .
3. index.html inicializa o aplicativo.
4. BlazorO roteador é carregado e o Razor Main componente é renderizado.

Na página Principal, selecionar o link para o About componente funciona no cliente


porque o Blazor roteador impede que o navegador faça uma solicitação na Internet para
www.contoso.com About e atende ao próprio componente renderizado About . Todas as

solicitações de pontos de extremidade internos dentro do Blazor WebAssembly aplicativo


funcionam da mesma maneira: as solicitações não disparam solicitações baseadas em
navegador para recursos hospedados pelo servidor na Internet. O roteador trata das
solicitações internamente.

Se uma solicitação for feita usando a barra de endereços do navegador para


www.contoso.com/About , a solicitação falhará. Este recurso não existe no host do
aplicativo na Internet; portanto, uma resposta 404 – Não Encontrado é retornada.

Como os navegadores fazem solicitações para hosts baseados na Internet para páginas
do lado do cliente, os servidores Web e os serviços de hospedagem devem reescrever
todas as solicitações de recursos que não estão fisicamente no servidor para a
index.html página. Quando index.html é retornado, o roteador do Blazor aplicativo
assume e responde com o recurso correto.

Ao implantar em um servidor IIS, você pode usar o Módulo de Reescrita de URL com o
arquivo publicado web.config do aplicativo. Para obter mais informações, consulte a
seção IIS .

Implantação hospedada com o ASP.NET Core


Uma implantação hospedada serve o Blazor WebAssembly aplicativo para navegadores
de um aplicativo ASP.NET Core que é executado em um servidor Web.

O aplicativo cliente Blazor WebAssembly é publicado na /bin/Release/{TARGET


FRAMEWORK}/publish/wwwroot pasta do aplicativo de servidor, juntamente com quaisquer
outros ativos Web estáticos do aplicativo de servidor. Os dois aplicativos são
implantados juntos. É necessário um servidor Web capaz de hospedar um aplicativo do
ASP.NET Core. Para uma implantação hospedada, o Visual Studio inclui o Blazor
WebAssembly modelo de projeto de aplicativo ( blazorwasm modelo ao usar o dotnet
new comando) com a opção Hosted selecionada ( -ho|--hosted ao usar o dotnet new
comando ).

Para obter mais informações, consulte os seguintes artigos:

ASP.NET Core hospedagem e implantação de aplicativos: hospedar e implantar


ASP.NET Core
Implantação em Serviço de Aplicativo do Azure: publicar um aplicativo ASP.NET
Core no Azure com o Visual Studio
Blazormodelos de projeto: ASP.NET Core Blazor estrutura do projeto

Implantação hospedada de um executável


dependente de estrutura para uma plataforma
específica
Para implantar um aplicativo hospedado Blazor WebAssembly como um executável
dependente de estrutura para uma plataforma específica (não independente) use as
diretrizes a seguir com base nas ferramentas em uso.

Visual Studio
Por padrão, uma implantação autocontida é configurada para um perfil de publicação
gerado ( .pubxml ). Confirme se o Server perfil de publicação do projeto contém a
<SelfContained> propriedade MSBuild definida false como .

No arquivo de .pubxml perfil de publicação na Server pasta do Properties projeto:

XML

<SelfContained>false</SelfContained>

Defina o RID (Identificador de Runtime) usando a configuração Runtime de Destino na


área Configurações da interface do usuário de Publicação , que gera a
<RuntimeIdentifier> propriedade MSBuild no perfil de publicação:

XML

<RuntimeIdentifier>{RID}</RuntimeIdentifier>

Na configuração anterior, o {RID} espaço reservado é o RID (Identificador de Runtime).

Publique o Server projeto na configuração versão .

7 Observação

É possível publicar um aplicativo com configurações de perfil de publicação usando


a CLI do .NET passando /p:PublishProfile={PROFILE} para o dotnet publish
comando , em que o {PROFILE} espaço reservado é o perfil. Para obter mais
informações, consulte as seções Publicar perfis e Publicar exemplo de publicação de
pastas no artigo Perfis de publicação do Visual Studio (.pubxml) para ASP.NET
Core implantação de aplicativo. Se você passar o RID no dotnet publish comando
e não no perfil de publicação, use a propriedade MSBuild ( /p:RuntimeIdentifier )
com o comando , não com a opção -r|--runtime .

CLI do .NET
Configure uma implantação autocontida colocando a <SelfContained> propriedade
MSBuild em um <PropertyGroup> no Server arquivo de projeto do projeto definido
false como :

XML
<SelfContained>false</SelfContained>

) Importante

A SelfContained propriedade deve ser colocada no Server arquivo de projeto do


projeto. A propriedade não pode ser definida corretamente com o dotnet publish
comando usando a opção --no-self-contained ou a propriedade
/p:SelfContained=false MSBuild .

Defina o RID (Identificador de Runtime) usando uma das seguintes abordagens:

Opção 1: definir o RID em um <PropertyGroup> no Server arquivo de projeto do


projeto:

XML

<RuntimeIdentifier>{RID}</RuntimeIdentifier>

Na configuração anterior, o {RID} espaço reservado é o RID (Identificador de


Runtime).

Publique o aplicativo na configuração versão do Server projeto:

CLI do .NET

dotnet publish -c Release

Opção 2: passe o RID no dotnet publish comando como a propriedade MSBuild


( /p:RuntimeIdentifier ), não com a opção -r|--runtime :

CLI do .NET

dotnet publish -c Release /p:RuntimeIdentifier={RID}

No comando anterior, o {RID} espaço reservado é o RID (Identificador de


Runtime).

Para obter mais informações, consulte os seguintes artigos:

Visão geral da publicação de aplicativos .NET


Hospedar e implantar o ASP.NET Core
Implantação hospedada com vários Blazor
WebAssembly aplicativos
Para obter mais informações, consulte Vários aplicativos ASP.NET Core Blazor
WebAssembly hospedados.

Implantação autônoma
Uma implantação autônoma serve ao Blazor WebAssembly aplicativo como um conjunto
de arquivos estáticos que são solicitados diretamente pelos clientes. Qualquer servidor
de arquivos estático pode atender ao Blazor aplicativo.

Os ativos de implantação autônomos são publicados na /bin/Release/{TARGET


FRAMEWORK}/publish/wwwroot pasta .

Serviço de aplicativo do Azure


Blazor WebAssemblyos aplicativos podem ser implantados no Azure App Services no
Windows, que hospeda o aplicativo no IIS.

Atualmente, não há suporte para a implantação de um aplicativo autônomo Blazor


WebAssembly no Serviço de Aplicativo do Azure para Linux. Recomendamos hospedar
um aplicativo autônomo Blazor WebAssembly usando Aplicativos Web Estáticos do
Azure, que dá suporte a esse cenário.

Aplicativos Web Estáticos do Azure


Para obter mais informações, consulte Tutorial: Criando um aplicativo Web estático com
Blazor no Aplicativos Web Estáticos do Azure.

IIS
O IIS é um servidor de arquivos estático capaz para Blazor aplicativos. Para configurar o
IIS para hospedar Blazoro , consulte Criar um site estático no IIS.

Os ativos publicados são criados na /bin/Release/{TARGET FRAMEWORK}/publish pasta ou


bin\Release\{TARGET FRAMEWORK}\browser-wasm\publish , dependendo de qual versão do
SDK é usada e onde o {TARGET FRAMEWORK} espaço reservado é a estrutura de destino.
Hospede o conteúdo da publish pasta no servidor Web ou no serviço de hospedagem.
web.config
Quando um Blazor projeto é publicado, um web.config arquivo é criado com a seguinte
configuração do IIS:

tipos MIME
A compactação HTTP está habilitada para os seguintes tipos MIME:
application/octet-stream
application/wasm

As regras do Módulo de Reescrita de URL são estabelecidas:


Atenda ao subdiretório em que residem os ativos estáticos do aplicativo
( wwwroot/{PATH REQUESTED} ).
Crie o roteamento de fallback do SPA para que as solicitações de ativos não
arquivos sejam redirecionadas para o documento padrão do aplicativo em sua
pasta de ativos estáticos ( wwwroot/index.html ).

Usar um personalizado web.config


Para usar um arquivo personalizado web.config :

1. Coloque o arquivo personalizado web.config na pasta raiz do projeto. Para uma


solução hospedadaBlazor WebAssembly, coloque o arquivo na Server pasta do
projeto.
2. Publique o projeto. Para uma solução hospedada Blazor WebAssembly , publique a
solução do Server projeto. Para obter mais informações, consulte Hospedar e
implantar ASP.NET Core Blazor.

Se a geração ou transformação do web.config SDK durante a publicação não mover o


arquivo para ativos publicados na publish pasta ou modificar a configuração
personalizada em seu arquivo personalizado web.config , use qualquer uma das
seguintes abordagens conforme necessário para assumir o controle total do processo:

Se o SDK não gerar o arquivo, por exemplo, em um aplicativo autônomo em ou ,


dependendo de qual versão do SDK é usada e onde o {TARGET FRAMEWORK} espaço
reservado é a estrutura de destino, defina a <PublishIISAssets> propriedade true
como no arquivo de projeto ( .csproj ). bin\Release\{TARGET FRAMEWORK}\browser-
wasm\publish /bin/Release/{TARGET FRAMEWORK}/publish/wwwroot Blazor

WebAssembly Normalmente, para aplicativos WebAssembly autônomos, essa é a


única configuração necessária para mover um arquivo personalizado web.config e
impedir a transformação do arquivo pelo SDK.
XML

<PropertyGroup>
<PublishIISAssets>true</PublishIISAssets>
</PropertyGroup>

Desabilitar a transformação do web.config SDK no arquivo de projeto ( .csproj ):

XML

<PropertyGroup>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>

Adicione um destino personalizado ao arquivo de projeto ( .csproj ) para mover


um arquivo personalizado web.config . No exemplo a seguir, o arquivo
personalizado web.config é colocado pelo desenvolvedor na raiz do projeto. Se o
web.config arquivo residir em outro lugar, especifique o caminho para o arquivo
em SourceFiles . O exemplo a seguir especifica a publish pasta com
$(PublishDir) , mas fornece um caminho para DestinationFolder para um local de

saída personalizado.

XML

<Target Name="CopyWebConfig" AfterTargets="Publish">


<Copy SourceFiles="web.config" DestinationFolder="$(PublishDir)" />
</Target>

Instalação do Módulo de Regeneração de URL

O Módulo de Reescrita de URL é necessário para reescrever URLs. Por padrão, o


módulo não está instalado e não está disponível para instalação como um recurso do
serviço de função do servidor Web (IIS). O módulo precisa ser baixado do site do IIS.
Use o Web Platform Installer para instalar o módulo:

1. Localmente, navegue até a página de downloads do Módulo de Reescrita de


URL . Para obter a versão em inglês, selecione WebPI para baixar o instalador do
WebPI. Para outros idiomas, selecione a arquitetura apropriada para o servidor
(x86/x64) para baixar o instalador.
2. Copie o instalador para o servidor. Execute o instalador. Selecione o botão Instalar
e aceite os termos de licença. Uma reinicialização do servidor não será necessária
após a conclusão da instalação.
Configuração do site
Defina o Caminho físico do site como a pasta do aplicativo. A pasta contém:

O web.config arquivo que o IIS usa para configurar o site, incluindo as regras de
redirecionamento necessárias e os tipos de conteúdo de arquivo.
A pasta de ativos estática do aplicativo.

Hospedar como um subapasta do IIS


Se um aplicativo autônomo estiver hospedado como um subapasta do IIS, execute um
dos seguintes procedimentos:

Desabilite o manipulador de módulo de ASP.NET Core herdado.

Remova o manipulador no arquivo publicado web.config do aplicativo


adicionando Blazor uma <handlers> seção à <system.webServer> seção do arquivo:

XML

<handlers>
<remove name="aspNetCore" />
</handlers>

Desabilite a herança da seção do <system.webServer> aplicativo raiz (pai) usando


um <location> elemento com definido false como inheritInChildApplications :

XML

<?xml version="1.0" encoding="utf-8"?>


<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" ... />
</handlers>
<aspNetCore ... />
</system.webServer>
</location>
</configuration>

7 Observação

Desabilitar a herança da seção do <system.webServer> aplicativo raiz (pai) é a


configuração padrão para aplicativos publicados usando o SDK do .NET.
Remover o manipulador ou desabilitar a herança é executado além de configurar o
caminho base do aplicativo. Defina o caminho base do aplicativo no arquivo do
index.html aplicativo para o alias do IIS usado ao configurar o subapasta no IIS.

Compactação Brotli e Gzip


Esta seção só se aplica a aplicativos autônomos Blazor WebAssembly . Os aplicativos
hospedados Blazor usam um arquivo de aplicativo web.config ASP.NET Core padrão, não
o arquivo vinculado nesta seção.

O IIS pode ser configurado por meio do web.config para fornecer ativos compactados
Blazor Brotli ou Gzip para aplicativos autônomos Blazor WebAssembly . Para obter um
exemplo de arquivo de configuração, consulte web.config .

A configuração adicional do arquivo de exemplo web.config pode ser necessária nos


seguintes cenários:

A especificação do aplicativo chama um dos seguintes:


Servindo arquivos compactados que não são configurados pelo arquivo de
exemplo web.config .
Servindo arquivos compactados configurados pelo arquivo de exemplo
web.config em um formato descompactado.

A configuração do IIS do servidor (por exemplo, applicationHost.config ) fornece


padrões de IIS no nível do servidor. Dependendo da configuração no nível do
servidor, o aplicativo pode exigir uma configuração diferente do IIS que o arquivo
de exemplo web.config contém.

Para obter mais informações sobre arquivos personalizados web.config , consulte a


seção Usar um personalizado web.config .

Solução de problemas
Se um 500 – Erro Interno do Servidor for recebido e o Gerenciador do IIS gerar erros ao
tentar acessar a configuração do site, confirme se o Módulo de Regeneração de URL
está instalado. Quando o módulo não está instalado, o web.config arquivo não pode
ser analisado pelo IIS. Isso impede que o Gerenciador do IIS carregue a configuração do
site e o site de fornecer Blazorarquivos estáticos.

Para obter mais informações sobre como solucionar problemas de implantações no IIS,
consulte Solucionar problemas de ASP.NET Core no Serviço de Aplicativo do Azure e no
IIS.
Armazenamento do Azure
A hospedagem de arquivos estáticos do Armazenamento do Azure permite a
hospedagem de aplicativos sem Blazor servidor. Nomes de domínio personalizados,
CDN (Rede de Distribuição de Conteúdo) do Azure e HTTPS são compatíveis.

Quando o serviço de blob está habilitado para hospedagem de site estático em uma
conta de armazenamento:

Defina o Nome do documento de índice como index.html .


Defina o Caminho do documento de erro como index.html . Razor componentes
e outros pontos de extremidade não arquivos não residem em caminhos físicos no
conteúdo estático armazenado pelo serviço blob. Quando uma solicitação para um
desses recursos é recebida que o Blazor roteador deve manipular, o erro 404 –
Não Encontrado gerado pelo serviço blob roteia a solicitação para o caminho do
documento erro. O index.html blob é retornado e o Blazor roteador carrega e
processa o caminho.

Se os arquivos não forem carregados em runtime devido a tipos MIME inadequados nos
cabeçalhos dos Content-Type arquivos, execute uma das seguintes ações:

Configure suas ferramentas para definir os tipos MIME corretos ( Content-Type


cabeçalhos) quando os arquivos forem implantados.

Altere os tipos MIME ( Content-Type cabeçalhos) para os arquivos depois que o


aplicativo for implantado.

Em Gerenciador de Armazenamento (portal do Azure) para cada arquivo:

1. Clique com o botão direito do mouse no arquivo e selecione Propriedades.


2. Defina o ContentType e selecione o botão Salvar .

Para mais informações, confira Hospedagem de site estático no Armazenamento do


Azure.

Nginx
nginx.conf O arquivo a seguir é simplificado para mostrar como configurar o Nginx

para enviar o index.html arquivo sempre que ele não encontrar um arquivo
correspondente no disco.
events { }
http {
server {
listen 80;

location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html =404;
}
}
}

Ao definir o limite de taxa de intermitência NGINX com limit_req , Blazor


WebAssembly os aplicativos podem exigir um valor de parâmetro grande burst para
acomodar o número relativamente grande de solicitações feitas por um aplicativo.
Inicialmente, defina o valor como pelo menos 60:

http {
server {
...

location / {
...

limit_req zone=one burst=60 nodelay;


}
}
}

Aumente o valor se as ferramentas de desenvolvedor do navegador ou uma ferramenta


de tráfego de rede indicarem que as solicitações estão recebendo um código de status
503 – Serviço Indisponível .

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

Apache
Para implantar um Blazor WebAssembly aplicativo no CentOS 7 ou posterior:

1. Crie o arquivo de configuração do Apache. O exemplo a seguir é um arquivo de


configuração simplificado ( blazorapp.config ):
<VirtualHost *:80>
ServerName www.example.com
ServerAlias *.example.com

DocumentRoot "/var/www/blazorapp"
ErrorDocument 404 /index.html

AddType application/wasm .wasm


AddType application/octet-stream .dll

<Directory "/var/www/blazorapp">
Options -Indexes
AllowOverride None
</Directory>

<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE application/octet-stream
AddOutputFilterByType DEFLATE application/wasm
<IfModule mod_setenvif.c>
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4.0[678] no-gzip
BrowserMatch bMSIE !no-gzip !gzip-only-text/html
</IfModule>
</IfModule>

ErrorLog /var/log/httpd/blazorapp-error.log
CustomLog /var/log/httpd/blazorapp-access.log common
</VirtualHost>

2. Coloque o arquivo de configuração do Apache no /etc/httpd/conf.d/ diretório ,


que é o diretório de configuração do Apache padrão no CentOS 7.

3. Coloque os arquivos do aplicativo no /var/www/blazorapp diretório (o local


especificado para DocumentRoot no arquivo de configuração).

4. Reinicie o serviço Apache.

Para obter mais informações, consulte mod_mime e mod_deflate .

GitHub Pages
O GitHub Action padrão, que implanta páginas, ignora a implantação de pastas
começando com sublinhado, por exemplo, a _framework pasta . Para implantar pastas
começando com sublinhado, adicione um arquivo vazio .nojekyll ao branch do Git.
O Git trata arquivos JavaScript (JS), como blazor.webassembly.js , como texto e converte
terminações de linha de CRLF (feed de linha de retorno de carro) para LF (feed de linha)
no pipeline de implantação. Essas alterações nos JS arquivos produzem hashes de
arquivo diferentes dos Blazor enviados para o cliente no blazor.boot.json arquivo. As
incompatibilidades resultam em falhas de verificação de integridade no cliente. Uma
abordagem para resolver esse problema é adicionar um arquivo com *.js binary linha
.gitattributes antes de adicionar os ativos do aplicativo ao branch do Git. A *.js
binary linha configura o Git para tratar JS arquivos como arquivos binários, o que evita

o processamento dos arquivos no pipeline de implantação. Os hashes de arquivo dos


arquivos não processados correspondem às entradas no arquivo e as blazor.boot.json
verificações de integridade do lado do cliente são aprovadas. Para obter mais
informações, consulte a seção Resolver falhas de verificação de integridade .

Para lidar com reescritas de URL, adicione um wwwroot/404.html arquivo com um script
que manipula o redirecionamento da solicitação para a index.html página. Para obter
um exemplo, consulte o repositório GitHub
SteveSandersonMS/BlazorOnGitHubPages :

wwwroot/404.html
Site ao vivo

Ao usar um site de projeto em vez de um site da organização, atualize a <base> marca


em wwwroot/index.html . Defina o valor do href atributo como o nome do repositório
GitHub com uma barra à direita (por exemplo, /my-repository/ ). No repositório GitHub
SteveSandersonMS/BlazorOnGitHubPages , a base href é atualizada na publicação
pelo.github/workflows/main.yml arquivo de configuração .

7 Observação

O repositório GitHub SteveSandersonMS/BlazorOnGitHubPages não é de


propriedade, mantido ou suportado pelo .NET Foundation ou Microsoft.

Autônomo com Docker


Um aplicativo autônomo Blazor WebAssembly é publicado como um conjunto de
arquivos estáticos para hospedagem por um servidor de arquivos estático.

Para hospedar o aplicativo no Docker:

Escolha um contêiner do Docker com suporte de servidor Web, como Ngnix ou


Apache.
Copie os ativos de publish pasta para uma pasta de localização definida no
servidor Web para fornecer arquivos estáticos.
Aplique configuração adicional conforme necessário para atender ao Blazor
WebAssembly aplicativo.

Para obter diretrizes de configuração, consulte os seguintes recursos:

Seção Nginx ou seção Apache deste artigo


Documentação do Docker

Valores de configuração do host


Blazor WebAssembly os aplicativos podem aceitar os seguintes valores de configuração
de host como argumentos de linha de comando em runtime no ambiente de
desenvolvimento.

Raiz do conteúdo
O --contentroot argumento define o caminho absoluto para o diretório que contém os
arquivos de conteúdo do aplicativo (raiz de conteúdo). Nos exemplos a seguir,
/content-root-path é o caminho raiz do conteúdo do aplicativo.

Passe o argumento ao executar o aplicativo localmente em um prompt de


comando. No diretório do aplicativo, execute:

CLI do .NET

dotnet run --contentroot=/content-root-path

Adicione uma entrada ao arquivo do launchSettings.json aplicativo no perfil IIS


Express. Esta configuração é usada quando o aplicativo é executado com o
Depurador do Visual Studio e em um prompt de comando com dotnet run .

JSON

"commandLineArgs": "--contentroot=/content-root-path"

No Visual Studio, especifique o argumento em


Propriedades>Depurar>argumentos do aplicativo. Definir o argumento na
página de propriedades do Visual Studio adiciona o argumento ao
launchSettings.json arquivo.
Console

--contentroot=/content-root-path

Caminho base
O --pathbase argumento define o caminho base do aplicativo para um aplicativo
executado localmente com um caminho de URL relativa não raiz (a <base> marca href
é definida como um caminho diferente de / preparo e produção). Nos exemplos a
seguir, /relative-URL-path é o caminho base do aplicativo. Para obter mais
informações, consulte Caminho base do aplicativo.

) Importante

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

Passe o argumento ao executar o aplicativo localmente em um prompt de


comando. No diretório do aplicativo, execute:

CLI do .NET

dotnet run --pathbase=/relative-URL-path

Adicione uma entrada ao arquivo do launchSettings.json aplicativo no perfil IIS


Express. Esta configuração é usada ao executar o aplicativo com o Depurador do
Visual Studio e em um prompt de comando com dotnet run .

JSON

"commandLineArgs": "--pathbase=/relative-URL-path"

No Visual Studio, especifique o argumento em


Propriedades>Depurar>argumentos do aplicativo. Definir o argumento na
página de propriedades do Visual Studio adiciona o argumento ao
launchSettings.json arquivo.
Console

--pathbase=/relative-URL-path

URLs
O argumento --urls define 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:

CLI do .NET

dotnet run --urls=http://127.0.0.1:0

Adicione uma entrada ao arquivo do launchSettings.json aplicativo no perfil IIS


Express. Esta configuração é usada ao executar o aplicativo com o Depurador do
Visual Studio e em um prompt de comando com dotnet run .

JSON

"commandLineArgs": "--urls=http://127.0.0.1:0"

No Visual Studio, especifique o argumento em


Propriedades>Depurar>argumentos do aplicativo. Definir o argumento na
página de propriedades do Visual Studio adiciona o argumento ao
launchSettings.json arquivo.

Console

--urls=http://127.0.0.1:0

Implantação hospedada no Linux (Nginx)


Configure o aplicativo com ForwardedHeadersOptions para encaminhar os X-Forwarded-
For cabeçalhos e X-Forwarded-Proto seguindo as diretrizes em Configurar ASP.NET Core

para trabalhar com servidores proxy e balanceadores de carga.

Para obter mais informações sobre como definir o caminho base do aplicativo, incluindo
a configuração do caminho do subapasta, consulte Hospedar e implantar ASP.NET Core
Blazor.

Siga as diretrizes para um aplicativo ASP.NET Core SignalR com as seguintes alterações:

Remova a configuração de buffer de proxy ( proxy_buffering off; ) porque a


configuração só se aplica a Eventos Enviados pelo Servidor (SSE), que não são
relevantes para Blazor interações cliente-servidor do aplicativo.

Altere o location caminho de /hubroute ( location /hubroute { ... } ) para o


caminho /{PATH} do subapasta ( location /{PATH} { ... } ), em que o {PATH}
espaço reservado é o caminho do subapasta.

O exemplo a seguir configura o servidor para um aplicativo que responde a


solicitações no caminho / raiz :

http {
server {
...
location / {
...
}
}
}

O exemplo a seguir configura o caminho do subapasta de /blazor :

http {
server {
...
location /blazor {
...
}
}
}

Para obter mais informações e diretrizes de configuração, consulte os seguintes


recursos:

Host ASP.NET Core no Linux com Nginx


Documentação do Nginx:
NGINX como um proxy WebSocket
Proxy do WebSocket
Desenvolvedores em fóruns de suporte não Microsoft:
Stack Overflow (marca: blazor)
Equipe do Slack do ASP.NET Core
Blazor Gitter

Configurar o cortador
Blazor executa o corte il (linguagem intermediária) em cada build de versão para
remover IL desnecessária dos assemblies de saída. Para obter mais informações,
consulte Configurar o filtro para ASP.NET Core Blazor.

Alterar a extensão de nome de arquivo dos


arquivos DLL
Caso você precise alterar as extensões de nome de arquivo dos arquivos publicados
.dll do aplicativo, siga as diretrizes nesta seção.

Depois de publicar o aplicativo, use um script de shell ou um pipeline de build do


DevOps para renomear .dll arquivos para usar uma extensão de arquivo diferente no
diretório da saída publicada do aplicativo.

Nos seguintes exemplos:

O PowerShell (PS) é usado para atualizar as extensões de arquivo.


.dll os arquivos são renomeados para usar a .bin extensão de arquivo da linha

de comando.
Os arquivos listados no arquivo publicado blazor.boot.json com uma .dll
extensão de arquivo são atualizados para a extensão de .bin arquivo.
Se os ativos de trabalho de serviço também estiverem em uso, um comando do
PowerShell atualizará os .dll arquivos listados no service-worker-assets.js
arquivo para a extensão de .bin arquivo.

Para usar uma extensão de arquivo diferente de .bin , substitua .bin nos comandos a
seguir pela extensão de arquivo desejada.

No Windows:

PowerShell

dir {PATH} | rename-item -NewName { $_.name -replace ".dll\b",".bin" }


((Get-Content {PATH}\blazor.boot.json -Raw) -replace '.dll"','.bin"') | Set-
Content {PATH}\blazor.boot.json
No comando anterior, o {PATH} espaço reservado é o caminho para a pasta publicada
_framework (por exemplo, .\bin\Release\net6.0\browser-
wasm\publish\wwwroot\_framework da pasta raiz do projeto).

Se os ativos de trabalho de serviço também estiverem em uso:

PowerShell

((Get-Content {PATH}\service-worker-assets.js -Raw) -replace


'.dll"','.bin"') | Set-Content {PATH}\service-worker-assets.js

No comando anterior, o {PATH} espaço reservado é o caminho para o arquivo


publicado service-worker-assets.js .

No Linux ou no macOS:

Console

for f in {PATH}/*; do mv "$f" "`echo $f | sed -e 's/\.dll/.bin/g'`"; done


sed -i 's/\.dll"/.bin"/g' {PATH}/blazor.boot.json

No comando anterior, o {PATH} espaço reservado é o caminho para a pasta publicada


_framework (por exemplo, .\bin\Release\net6.0\browser-
wasm\publish\wwwroot\_framework da pasta raiz do projeto).

Se os ativos de trabalho de serviço também estiverem em uso:

Console

sed -i 's/\.dll"/.bin"/g' {PATH}/service-worker-assets.js

No comando anterior, o {PATH} espaço reservado é o caminho para o arquivo


publicado service-worker-assets.js .

Para abordar os arquivos compactados blazor.boot.json.gz e blazor.boot.json.br ,


adote uma das seguintes abordagens:

Remova os arquivos compactados blazor.boot.json.gz e blazor.boot.json.br . A


compactação está desabilitada com essa abordagem.
Compacte novamente o arquivo atualizado blazor.boot.json .

As diretrizes anteriores para o arquivo compactado blazor.boot.json também se


aplicam quando os ativos de trabalho de serviço estão em uso. Remova ou recompacte
service-worker-assets.js.br e service-worker-assets.js.gz . Caso contrário, as

verificações de integridade do arquivo falharão no navegador.

O exemplo do Windows a seguir para .NET 6.0 usa um script do PowerShell colocado na
raiz do projeto. O script a seguir, que desabilita a compactação, é a base para
modificações adicionais se você quiser recompactar o blazor.boot.json arquivo.

ChangeDLLExtensions.ps1: :

PowerShell

param([string]$filepath,[string]$tfm)
dir $filepath\bin\Release\$tfm\browser-wasm\publish\wwwroot\_framework |
rename-item -NewName { $_.name -replace ".dll\b",".bin" }
((Get-Content $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\blazor.boot.json -Raw) -replace
'.dll"','.bin"') | Set-Content $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\blazor.boot.json
Remove-Item $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\blazor.boot.json.gz
Remove-Item $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\blazor.boot.json.br

Se os ativos de trabalho de serviço também estiverem em uso, adicione os seguintes


comandos:

PowerShell

((Get-Content $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\service-worker-assets.js -Raw) -replace
'.dll"','.bin"') | Set-Content $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\wwwroot\service-worker-assets.js
Remove-Item $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\wwwroot\service-worker-assets.js.gz
Remove-Item $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\wwwroot\service-worker-assets.js.br

No arquivo de projeto, o script é executado depois de publicar o aplicativo para a


Release configuração:

XML

<Target Name="ChangeDLLFileExtensions" AfterTargets="AfterPublish"


Condition="'$(Configuration)'=='Release'">
<Exec Command="powershell.exe -command &quot;&amp; {
.\ChangeDLLExtensions.ps1 '$(SolutionDir)' '$(TargetFramework)'}&quot;" />
</Target>
7 Observação

Ao renomear e carregar lentamente os mesmos assemblies, consulte as diretrizes


em Assemblies de carregamento lentos no ASP.NET Core Blazor WebAssembly.

Normalmente, o servidor do aplicativo requer configuração de ativo estático para


atender aos arquivos com a extensão atualizada. Para um aplicativo hospedado pelo IIS,
adicione uma entrada de mapa MIME ( <mimeMap> ) para a nova extensão de arquivo na
seção de conteúdo estático ( <staticContent> ) em um arquivo personalizado web.config
. O exemplo a seguir pressupõe que a extensão de arquivo foi alterada de .dll para
.bin :

XML

<staticContent>
...
<mimeMap fileExtension=".bin" mimeType="application/octet-stream" />
...
</staticContent>

Inclua uma atualização para arquivos compactados se a compactação estiver em uso:

<mimeMap fileExtension=".bin.br" mimeType="application/octet-stream" />


<mimeMap fileExtension=".bin.gz" mimeType="application/octet-stream" />

Remova a entrada para a extensão de .dll arquivo:

diff

- <mimeMap fileExtension=".dll" mimeType="application/octet-stream" />

Remova entradas para arquivos compactados .dll se a compactação estiver em uso:

diff

- <mimeMap fileExtension=".dll.br" mimeType="application/octet-stream" />


- <mimeMap fileExtension=".dll.gz" mimeType="application/octet-stream" />

Para obter mais informações sobre arquivos personalizados web.config , consulte a


seção Usar um personalizado web.config .
Corrupção de implantação anterior
Normalmente na implantação:

Somente os arquivos que foram alterados são substituídos, o que geralmente


resulta em uma implantação mais rápida.
Os arquivos existentes que não fazem parte da nova implantação são deixados em
vigor para uso pela nova implantação.

Em casos raros, arquivos persistentes de uma implantação anterior podem corromper


uma nova implantação. Excluir completamente a implantação existente (ou aplicativo
publicado localmente antes da implantação) pode resolver o problema com uma
implantação corrompida. Geralmente, excluir a implantação existente uma vez é
suficiente para resolver o problema, inclusive para um pipeline de build e implantação
do DevOps.

Se você determinar que a limpeza de uma implantação anterior é sempre necessária


quando um pipeline de build e implantação do DevOps está em uso, você pode
adicionar temporariamente uma etapa ao pipeline de build para excluir a implantação
anterior para cada nova implantação até que você solucione a causa exata da corrupção.

Resolver falhas de verificação de integridade


Quando Blazor WebAssembly baixa os arquivos de inicialização de um aplicativo, ele
instrui o navegador a executar verificações de integridade nas respostas. Blazor envia
valores de hash SHA-256 para DLL ( .dll ), WebAssembly ( .wasm ) e outros arquivos no
blazor.boot.json arquivo, que não são armazenados em cache em clientes. Os hashes
de arquivo de arquivos armazenados em cache são comparados aos hashes no
blazor.boot.json arquivo. Para arquivos armazenados em cache com um hash
correspondente, Blazor o usa os arquivos armazenados em cache. Caso contrário, os
arquivos serão solicitados do servidor. Depois que um arquivo é baixado, seu hash é
verificado novamente para validação de integridade. Um erro será gerado pelo
navegador se a verificação de integridade de qualquer arquivo baixado falhar.

BlazorAlgoritmo de para gerenciar a integridade do arquivo:

Garante que o aplicativo não corre o risco de carregar um conjunto inconsistente


de arquivos, por exemplo, se uma nova implantação for aplicada ao servidor Web
enquanto o usuário estiver em processo de download dos arquivos do aplicativo.
Arquivos inconsistentes podem resultar em um aplicativo com defeito.
Garante que o navegador do usuário nunca armazene em cache respostas
inconsistentes ou inválidas, o que pode impedir que o aplicativo seja iniciado
mesmo que o usuário atualize manualmente a página.
Torna seguro armazenar em cache as respostas e não verificar se há alterações no
lado do servidor até que os hashes SHA-256 esperados sejam alterados, portanto,
as cargas de página subsequentes envolvem menos solicitações e são concluídas
mais rapidamente.

Se o servidor Web retornar respostas que não correspondem aos hashes SHA-256
esperados, um erro semelhante ao exemplo a seguir aparecerá no console do
desenvolvedor do navegador:

Falha ao localizar um resumo válido no atributo 'integrity' para o recurso


'https://myapp.example.com/_framework/MyBlazorApp.dll' com integridade SHA-
256 computada 'IIa70iwvmEg5WiDV17OpQ5eCztNYqL186J56852RpJY='. O recurso
foi bloqueado.

Na maioria dos casos, o aviso não indica um problema com a verificação de integridade.
Em vez disso, o aviso geralmente significa que existe algum outro problema.

Para Blazor WebAssemblyobter a origem de referência de inicialização, consulte o


Boot.WebAssembly.ts arquivo no repositório GitHubdotnet/aspnetcore .

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Diagnosticando problemas de integridade


Quando um aplicativo é criado, o manifesto gerado blazor.boot.json descreve os
hashes SHA-256 dos recursos de inicialização no momento em que a saída do build é
produzida. A verificação de integridade passa desde que os hashes SHA-256
correspondam blazor.boot.json aos arquivos entregues ao navegador.

Os motivos comuns pelos quais isso falha são:

A resposta do servidor Web é um erro (por exemplo, um erro 404 – Não


encontrado ou 500 – Erro interno do servidor) em vez do arquivo solicitado pelo
navegador. Isso é relatado pelo navegador como uma falha de verificação de
integridade e não como uma falha de resposta.
Algo alterou o conteúdo dos arquivos entre o build e a entrega dos arquivos no
navegador. Isso pode acontecer:
Se você ou as ferramentas de build modificarem manualmente a saída do build.
Se algum aspecto do processo de implantação tiver modificado os arquivos. Por
exemplo, se você usar um mecanismo de implantação baseado em Git, tenha
em mente que o Git converte de forma transparente as terminações de linha no
estilo Do Windows em terminações de linha no estilo Unix se você confirmar
arquivos no Windows e fazer check-out deles no Linux. Alterar terminações de
linha de arquivo altera os hashes SHA-256. Para evitar esse problema, considere
usar .gitattributes para tratar artefatos de build como binary arquivos .
O servidor Web modifica o conteúdo do arquivo como parte de servi-los. Por
exemplo, algumas CDNs (redes de distribuição de conteúdo) tentam minificar o
HTML automaticamente, modificando-o assim. Talvez seja necessário desabilitar
esses recursos.
O blazor.boot.json arquivo falha ao carregar corretamente ou é armazenado em
cache incorretamente no cliente. As causas comuns incluem um dos seguintes:
Código de desenvolvedor personalizado configurado incorretamente ou com
mau funcionamento.
Uma ou mais camadas de cache intermediárias configuradas incorretamente.

Para diagnosticar qual delas se aplica no seu caso:

1. Observe qual arquivo está disparando o erro lendo a mensagem de erro.


2. Abra as ferramentas de desenvolvedor do navegador e procure na guia Rede . Se
necessário, recarregue a página para ver a lista de solicitações e respostas. Localize
o arquivo que está disparando o erro nessa lista.
3. Verifique o código de status HTTP na resposta. Se o servidor retornar algo
diferente de 200 – OK (ou outro código de status 2xx), você terá um problema no
lado do servidor para diagnosticar. Por exemplo, o código de status 403 significa
que há um problema de autorização, enquanto o código de status 500 significa
que o servidor está falhando de maneira não especificada. Consulte logs do lado
do servidor para diagnosticar e corrigir o aplicativo.
4. Se o código de status for 200 – OK para o recurso, examine o conteúdo da
resposta nas ferramentas de desenvolvedor do navegador e verifique se o
conteúdo corresponde aos dados esperados. Por exemplo, um problema comum é
configurar incorretamente o roteamento para que as solicitações retornem seus
index.html dados mesmo para outros arquivos. Verifique se as respostas às .wasm

solicitações são binários WebAssembly e se as respostas às .dll solicitações são


binários de assembly do .NET. Caso contrário, você tem um problema de
roteamento do lado do servidor para diagnosticar.
5. Procure validar a saída publicada e implantada do aplicativo com o script
solucionar problemas de integridade do PowerShell.

Se você confirmar que o servidor está retornando dados plausivelmente corretos, deve
haver outra coisa modificando o conteúdo entre o build e a entrega do arquivo. Para
investigar isso:

Examine a cadeia de ferramentas de build e o mecanismo de implantação caso


eles estejam modificando arquivos após a criação dos arquivos. Um exemplo disso
é quando o Git transforma terminações de linha de arquivo, conforme descrito
anteriormente.
Examine o servidor Web ou a configuração da CDN caso estejam configurados
para modificar as respostas dinamicamente (por exemplo, tentando minificar
HTML). É bom que o servidor Web implemente a compactação HTTP (por exemplo,
retornando content-encoding: br ou content-encoding: gzip ), pois isso não afeta
o resultado após a descompactação. No entanto, não é bom que o servidor Web
modifique os dados descompactados.

Solucionar problemas de integridade do script do


PowerShell
Use o script do integrity.ps1 PowerShell para validar um aplicativo publicado e
implantado Blazor . O script é fornecido para o PowerShell Core 7 ou posterior como um
ponto de partida quando o aplicativo tem problemas de integridade que a Blazor
estrutura não pode identificar. A personalização do script pode ser necessária para seus
aplicativos, inclusive se estiver em execução na versão do PowerShell posterior à versão
7.2.0.

O script verifica os arquivos na publish pasta e baixado do aplicativo implantado para


detectar problemas nos diferentes manifestos que contêm hashes de integridade. Essas
verificações devem detectar os problemas mais comuns:

Você modificou um arquivo na saída publicada sem perceber.


O aplicativo não foi implantado corretamente no destino de implantação ou algo
alterado no ambiente do destino de implantação.
Há diferenças entre o aplicativo implantado e a saída da publicação do aplicativo.

Invoque o script com o seguinte comando em um shell de comando do PowerShell:

PowerShell
.\integrity.ps1 {BASE URL} {PUBLISH OUTPUT FOLDER}

No exemplo a seguir, o script é executado em um aplicativo em execução local em


https://localhost:5001/ :

PowerShell

.\integrity.ps1 https://localhost:5001/
C:\TestApps\BlazorSample\bin\Release\net6.0\publish\

Espaços:

{BASE URL} : a URL do aplicativo implantado. Uma barra à direita ( / ) é necessária.


{PUBLISH OUTPUT FOLDER} : o caminho para a pasta ou o local do publish aplicativo

em que o aplicativo é publicado para implantação.

7 Observação

Ao clonar o dotnet/AspNetCore.Docs repositório GitHub, o integrity.ps1 script


pode ser colocado em quarentena pelo Bitdefender ou por outro scanner de
vírus presente no sistema. Normalmente, o arquivo é preso pela tecnologia de
verificação heurística de um verificador de vírus, que apenas procura padrões em
arquivos que podem indicar a presença de malware. Para impedir que o verificador
de vírus insira o arquivo em quarentena, adicione uma exceção ao verificador de
vírus antes de clonar o repositório. O exemplo a seguir é um caminho típico para o
script em um sistema Windows. Ajuste o caminho conforme necessário para outros
sistemas. O espaço reservado {USER} é o segmento de caminho do usuário.

C:\Users\{USER}\Documents\GitHub\AspNetCore.Docs\aspnetcore\blazor\host-
and-deploy\webassembly\_samples\integrity.ps1

Aviso: a criação de exceções de scanner de vírus é perigosa e só deve ser executada


quando você tiver certeza de que o arquivo está seguro.

Comparar a soma de verificação de um arquivo com um valor de soma de


verificação válido não garante a segurança do arquivo, mas modificar um arquivo
de uma maneira que mantenha um valor de soma de verificação não é trivial para
usuários mal-intencionados. Portanto, as somas de verificação são úteis como uma
abordagem de segurança geral. Compare a soma de verificação do arquivo local
integrity.ps1 com um dos seguintes valores:
SHA256: 32c24cb667d79a701135cb72f6bae490d81703323f61b8af2c7e5e5dc0f0c2bb
MD5: 9cee7d7ec86ee809a329b5406fbf21a8

Obtenha a soma de verificação do arquivo no sistema operacional Windows com o


comando a seguir. Forneça o caminho e o nome do arquivo para o {PATH AND FILE
NAME} espaço reservado e indique o tipo de soma de verificação a ser produzida
para o {SHA512|MD5} espaço reservado ou SHA256 MD5 :

Console

CertUtil -hashfile {PATH AND FILE NAME} {SHA256|MD5}

Se você tiver alguma preocupação de que a validação de soma de verificação não


seja segura o suficiente em seu ambiente, consulte a liderança de segurança da sua
organização para obter diretrizes.

Para obter mais informações, consulte Noções básicas sobre malware & de outras
ameaças.

Desabilitar a verificação de integridade para aplicativos


não PWA
Na maioria dos casos, não desabilite a verificação de integridade. Desabilitar a
verificação de integridade não resolve o problema subjacente que causou as respostas
inesperadas e resulta na perda dos benefícios listados anteriormente.

Pode haver casos em que o servidor Web não pode ser confiado para retornar respostas
consistentes e você não tem escolha a não ser desabilitar temporariamente as
verificações de integridade até que o problema subjacente seja resolvido.

Para desabilitar verificações de integridade, adicione o seguinte a um grupo de


propriedades no Blazor WebAssembly arquivo de projeto do aplicativo ( .csproj ):

XML

<BlazorCacheBootResources>false</BlazorCacheBootResources>

BlazorCacheBootResources também desabilita Blazoro comportamento padrão de

armazenar em cache o .dll , .wasm e outros arquivos com base em seus hashes SHA-
256 porque a propriedade indica que os hashes SHA-256 não podem ser confiados para
correção. Mesmo com essa configuração, o cache HTTP normal do navegador ainda
pode armazenar esses arquivos em cache, mas se isso acontece depende ou não da
configuração do servidor Web e dos cache-control cabeçalhos que ele atende.

7 Observação

A BlazorCacheBootResources propriedade não desabilitar verificações de


integridade para PWAs (Aplicativos Web Progressivos). Para obter diretrizes
relativas a PWAs, consulte a seção Desabilitar verificação de integridade para
PWAs .

Não é possível fornecer uma lista completa de cenários em que a desabilitação da


verificação de integridade é necessária. Os servidores podem responder a uma
solicitação de maneiras arbitrárias fora do escopo da Blazor estrutura. A estrutura
fornece a BlazorCacheBootResources configuração para tornar o aplicativo executável ao
custo de perder uma garantia de integridade que o aplicativo pode fornecer. Novamente,
não recomendamos desabilitar a verificação de integridade, especialmente para
implantações de produção. Os desenvolvedores devem procurar resolver o problema de
integridade subjacente que está causando falha na verificação de integridade.

Alguns casos gerais que podem causar problemas de integridade são:

Em execução em HTTP, onde a integridade não pode ser verificada.


Se o processo de implantação modificar os arquivos após a publicação de alguma
forma.
Se o host modificar os arquivos de alguma forma.

Desabilitar a verificação de integridade para PWAs


BlazorO modelo PWA (Aplicativo Web Progressivo) contém um arquivo sugerido
service-worker.published.js responsável por buscar e armazenar arquivos de aplicativo

para uso offline. Esse é um processo separado do mecanismo normal de inicialização de


aplicativo e tem sua própria lógica de verificação de integridade separada.

Dentro do service-worker.published.js arquivo, a seguinte linha está presente:

JavaScript

.map(asset => new Request(asset.url, { integrity: asset.hash }));

Para desabilitar a verificação de integridade, remova o integrity parâmetro alterando a


linha para o seguinte:
JavaScript

.map(asset => new Request(asset.url));

Novamente, desabilitar a verificação de integridade significa que você perde as


garantias de segurança oferecidas pela verificação de integridade. Por exemplo, há o
risco de que, se o navegador do usuário estiver armazenando em cache o aplicativo no
momento exato em que você implantar uma nova versão, ele poderá armazenar em
cache alguns arquivos da implantação antiga e alguns da nova implantação. Se isso
acontecer, o aplicativo ficará preso em um estado quebrado até que você implante uma
nova atualização.

Configuração de SignalR
SignalRAs condições de hospedagem e dimensionamento se aplicam a Blazor
aplicativos que usam SignalR.

Transportes
Blazor funciona melhor ao usar WebSockets como transporte SignalR devido à menor
latência, melhor confiabilidade e segurança aprimorada. A Sondagem Longa é usada
por SignalR quando WebSockets não está disponível ou quando o aplicativo está
explicitamente configurado para usar a Sondagem Longa. Ao implantar em Serviço de
Aplicativo do Azure, configure o aplicativo para usar WebSockets nas configurações de
portal do Azure para o serviço. Para obter detalhes sobre como configurar o aplicativo
para Serviço de Aplicativo do Azure, consulte as SignalR diretrizes de publicação.

Um aviso do console será exibido se a Sondagem Longa for utilizada:

Falha ao se conectar por meio de WebSockets, usando o transporte de fallback de


Sondagem Longa. Isso pode ser devido a uma VPN ou proxy bloqueando a
conexão.

Falhas globais de implantação e conexão


Recomendações para implantações globais em data centers geográficos:

Implante o aplicativo nas regiões em que a maioria dos usuários reside.


Leve em consideração o aumento da latência do tráfego entre continentes.
Para hospedagem do Azure, use o Serviço do AzureSignalR.
Se um aplicativo implantado frequentemente exibir a interface do usuário de reconexão
devido a tempos limite de ping causados pela latência da Internet, alonge os tempos
limite do servidor e do cliente:

Servidor

Pelo menos o dobro do tempo máximo de ida e volta esperado entre o cliente e o
servidor. Teste, monitore e revise os tempos limite conforme necessário. Para o
SignalR hub, defina o ClientTimeoutInterval (padrão: 30 segundos) e
HandshakeTimeout (padrão: 15 segundos). O exemplo a seguir pressupõe que
KeepAliveInterval usa o valor padrão de 15 segundos.

) Importante

O KeepAliveInterval não está diretamente relacionado à interface do usuário


de reconexão que aparece. O intervalo de Keep-Alive não precisa
necessariamente ser alterado. Se o problema de aparência da interface do
usuário de reconexão for devido a tempos limite, o ClientTimeoutInterval e
HandshakeTimeout poderão ser aumentados e o intervalo de Keep-Alive
poderá permanecer o mesmo. A consideração importante é que, se você
alterar o intervalo de Keep-Alive, verifique se o valor de tempo limite do
cliente é pelo menos o dobro do valor do intervalo de Keep-Alive e se o
intervalo de Keep-Alive no cliente corresponde à configuração do servidor.

No exemplo a seguir, o ClientTimeoutInterval é aumentado para 60


segundos e o HandshakeTimeout é aumentado para 30 segundos.

Para um aplicativo hospedado Blazor WebAssembly no Program.cs Server projeto:

C#

builder.Services.AddSignalR(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Para obter mais informações, consulte ASP.NET Core BlazorSignalR diretrizes.

Cliente

Normalmente, o dobro do valor usado para o servidor KeepAliveInterval definir o


tempo limite para o tempo limite do servidor do cliente (ServerTimeout, padrão:
30 segundos).
) Importante

O intervalo de Keep-Alive (KeepAliveInterval) não está diretamente


relacionado à interface do usuário de reconexão que aparece. O intervalo de
Keep-Alive não precisa necessariamente ser alterado. Se o problema de
aparência da interface do usuário de reconexão for devido a tempos limite, o
tempo limite do servidor poderá ser aumentado e o intervalo de Keep-Alive
poderá permanecer o mesmo. A consideração importante é que, se você
alterar o intervalo de Keep-Alive, verifique se o valor do tempo limite é pelo
menos o dobro do valor do intervalo de Keep-Alive e se o intervalo de Keep-
Alive no servidor corresponde à configuração do cliente.

No exemplo a seguir, um valor personalizado de 60 segundos é usado para o


tempo limite do servidor.

Ao criar uma conexão de hub em um componente, defina o ServerTimeout


(padrão: 30 segundos) e HandshakeTimeout (padrão: 15 segundos) no compilado
HubConnection.

O exemplo a seguir baseia-se no Index componente no SignalR tutorial com


Blazor. O tempo limite do servidor é aumentado para 60 segundos e o tempo
limite do handshake é aumentado para 30 segundos:

C#

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();

hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

hubConnection.On<string, string>("ReceiveMessage", (user, message)


=> ...

await hubConnection.StartAsync();
}

Ao alterar os valores do tempo limite do servidor (ServerTimeout) ou do intervalo


de Keep-Alive (KeepAliveInterval:
O tempo limite do servidor deve ser pelo menos o dobro do valor atribuído ao
intervalo de Keep-Alive.
O intervalo de Keep-Alive deve ser menor ou igual a metade do valor atribuído
ao tempo limite do servidor.

Para obter mais informações, consulte ASP.NET Core BlazorSignalR diretrizes.


Configurar o Trimmer para ASP.NET
Core Blazor
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Este artigo explica como controlar o Vinculador de Linguagem Intermediária (IL)


(Trimmer) ao criar um Blazor aplicativo.

Blazor WebAssembly executa o corte il (linguagem intermediária) para reduzir o


tamanho da saída publicada. Por padrão, o corte ocorre ao publicar um aplicativo.

O corte pode ter efeitos prejudiciais. Em aplicativos que usam reflexão, o Trimmer
geralmente não pode determinar os tipos necessários para reflexão no runtime. Para
cortar aplicativos que usam reflexão, o Trimmer deve ser informado sobre os tipos
necessários para reflexão no código do aplicativo e nos pacotes ou estruturas dos quais
o aplicativo depende. O Trimmer também não consegue reagir ao comportamento
dinâmico de um aplicativo em runtime. Para garantir que o aplicativo cortado funcione
corretamente uma vez implantado, teste a saída publicada com frequência durante o
desenvolvimento.

Para configurar o Trimmer, consulte o artigo de opções de corte na documentação de


conceitos básicos do .NET, que inclui diretrizes sobre os seguintes assuntos:

Desabilite o corte para todo o aplicativo com a <PublishTrimmed> propriedade no


arquivo de projeto.
Controlar o quão agressivamente não utilizado o IL é descartado pelo Trimmer.
Impeça o Trimmer de cortar assemblies específicos.
Assemblies "Raiz" para corte.
Avisos de superfície para tipos refletidos definindo a
<SuppressTrimAnalysisWarnings> propriedade false no arquivo de projeto.
Controle o corte de símbolos e o suporte ao depurador.
Defina os recursos do Trimmer para aparar recursos da biblioteca de estruturas.

Recursos adicionais
Cortar implantações e executáveis autossuficientes
Blazor ASP.NET Core práticas recomendadas de desempenho
Layout de implantação para aplicativos
Blazor WebAssembly ASP.NET Core
Artigo • 28/11/2022 • 12 minutos para o fim da leitura

Este artigo explica como habilitar Blazor WebAssembly implantações em ambientes que
bloqueiam o download e a execução de arquivos DLL (biblioteca de vínculo dinâmico).

Blazor WebAssembly os aplicativos exigem que as DLLs (bibliotecas de vínculo


dinâmico) funcionem, mas alguns ambientes impedem que os clientes baixem e
executem DLLs. Em um subconjunto desses ambientes, alterar a extensão de nome de
arquivo de arquivos DLL (.dll) é suficiente para ignorar as restrições de segurança, mas
os produtos de segurança geralmente são capazes de verificar o conteúdo de arquivos
que atravessam a rede e bloqueiam ou colocar em quarentena arquivos DLL. Este artigo
descreve uma abordagem para habilitar Blazor WebAssembly aplicativos nesses
ambientes, em que um arquivo de pacote de várias partes é criado a partir das DLLs do
aplicativo para que as DLLs possam ser baixadas juntas ignorando as restrições de
segurança.

Um aplicativo hospedado Blazor WebAssembly pode personalizar seus arquivos


publicados e empacotamento de DLLs de aplicativo usando os seguintes recursos:

Inicializadores JavaScript que permitem personalizar o Blazor processo de


inicialização.
Extensibilidade do MSBuild para transformar a lista de arquivos publicados e
definir Blazor Extensões de Publicação. Blazor As Extensões de Publicação são
arquivos definidos durante o processo de publicação que fornecem uma
representação alternativa para o conjunto de arquivos necessários para executar
um aplicativo publicado Blazor WebAssembly . Neste artigo, uma Blazor Extensão
de Publicação é criada que produz um pacote de várias partes com todas as DLLs
do aplicativo empacotadas em um único arquivo para que as DLLs possam ser
baixadas juntas.

A abordagem demonstrada neste artigo serve como um ponto de partida para os


desenvolvedores criarem suas próprias estratégias e processos de carregamento
personalizados.

2 Aviso

Qualquer abordagem tomada para contornar uma restrição de segurança deve ser
cuidadosamente considerada por suas implicações de segurança. Recomendamos
explorar o assunto ainda mais com os profissionais de segurança de rede da sua
organização antes de adotar a abordagem neste artigo. As alternativas a considerar
incluem:

Habilite dispositivos de segurança e software de segurança para permitir que


os clientes de rede baixem e usem os arquivos exatos exigidos por um Blazor
WebAssembly aplicativo.
Alterne do modelo de Blazor WebAssembly hospedagem para o Blazor
Server modelo de hospedagem, que mantém todo o código C# do aplicativo
no servidor e não requer o download de DLLs para clientes. Blazor Server
também oferece a vantagem de manter o código C# privado sem exigir o uso
de aplicativos de API Web para privacidade de código C# com Blazor
WebAssembly aplicativos.

Pacote e aplicativo de exemplo do NuGet


experimental
A abordagem descrita neste artigo é usada pelo pacote
experimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle
(NuGet.org) . O pacote contém destinos msbuild para personalizar a saída de Blazor
publicação e um inicializador JavaScript para usar um carregador de recursos de
inicialização personalizado, cada um dos quais são descritos em detalhes mais adiante
neste artigo.

Código experimental (inclui a origem do pacote NuGet e CustomPackagedApp o


aplicativo de exemplo)

2 Aviso

Os recursos experimentais e de visualização são fornecidos com a finalidade de


coletar comentários e não têm suporte para uso em produção. Para obter mais
informações e fornecer comentários para a unidade do produto ASP.NET Core,
consulte Considerar a liberação de uma versão com suporte do (dotnet
Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle /aspnetcore
#36978) .

Posteriormente neste artigo, a Personalização do Blazor WebAssembly processo de


carregamento por meio de uma seção de pacote NuGet com suas três subseções
fornece explicações detalhadas sobre a configuração e o código no
Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle pacote. As explicações
detalhadas são importantes para entender quando você cria sua própria estratégia e
processo de carregamento personalizado para Blazor WebAssembly aplicativos. Para
usar o pacote NuGet publicado, experimental e sem suporte sem personalização como
uma demonstração local, execute as seguintes etapas:

1. Use uma solução hospedada Blazor WebAssembly existente ou crie uma nova
solução a partir do modelo de projeto usando o Blazor WebAssembly Visual Studio
ou passando a opção-ho|--hosted para o dotnet new comando ( dotnet new
blazorwasm -ho ). Para obter mais informações, consulte Ferramentas para ASP.NET

Core Blazor.

2. Client No projeto, adicione o pacote


experimental Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle .

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET,


consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de
consumo de pacotes (documentação do NuGet). Confirme as versões
corretas de pacote em NuGet.org .

3. Server No projeto, adicione um ponto de extremidade para servir o arquivo de


pacote ( app.bundle ). O código de exemplo pode ser encontrado no pacote Servir
da seção de aplicativo do servidor host deste artigo.

4. Publicar o aplicativo na configuração de versão.

Personalizar o Blazor WebAssembly processo


de carregamento por meio de um pacote
NuGet

2 Aviso

As diretrizes nesta seção com suas três subseções pertencem à criação de um


pacote NuGet do zero para implementar sua própria estratégia e processo de
carregamento personalizado. O pacote
experimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle
(NuGet.org) baseia-se nas diretrizes nesta seção. Ao usar o pacote fornecido em
uma demonstração local da abordagem de download do pacote de várias partes,
você não precisa seguir as diretrizes nesta seção. Para obter diretrizes sobre como
usar o pacote fornecido, consulte o pacote NuGet experimental e a seção de
aplicativo de exemplo .

Blazor Os recursos do aplicativo são empacotados em um arquivo de pacote de várias


partes e carregados pelo navegador por meio de um inicializador JavaScript (JS)
personalizado. Para um aplicativo que consome o pacote com o JS inicializador, o
aplicativo requer apenas que o arquivo de pacote seja atendido quando solicitado.
Todos os outros aspectos dessa abordagem são tratados de forma transparente.

Quatro personalizações são necessárias para a forma como um aplicativo publicado


Blazor padrão carrega:

Uma tarefa do MSBuild para transformar os arquivos de publicação.


Um pacote NuGet com MSBuild destina-se ao Blazor processo de publicação,
transforma a saída e define um ou mais Blazor arquivos de extensão de publicação
(nesse caso, um único pacote).
Um JS inicializador para atualizar o retorno de chamada do Blazor WebAssembly
carregador de recursos para que ele carregue o pacote e forneça ao aplicativo os
arquivos individuais.
Um auxiliar no aplicativo host Server para garantir que o pacote seja atendido aos
clientes sob solicitação.

Criar uma tarefa do MSBuild para personalizar a lista de


arquivos publicados e definir novas extensões
Crie uma tarefa do MSBuild como uma classe C# pública que pode ser importada como
parte de uma compilação do MSBuild e que pode interagir com o build.

O seguinte é necessário para a classe C#:

Um novo projeto de biblioteca de classes.


Uma estrutura de destino do projeto de netstandard2.0 .
Referências a pacotes MSBuild:
Microsoft.Build.Framework
Microsoft.Build.Utilities.Core

7 Observação
O pacote NuGet para os exemplos neste artigo tem o nome do pacote fornecido
pela Microsoft. Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle
Para obter diretrizes sobre como nomear e produzir seu próprio pacote NuGet,
consulte os seguintes artigos do NuGet:

Melhoras práticas de criação de pacote


Reserva de prefixo da ID do pacote

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/Microsoft.AspNetC

ore.Components.WebAssembly.MultipartBundle.Tasks.csproj :

XML

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="
{VERSION}" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="
{VERSION}" />
</ItemGroup>

</Project>

Determine as versões mais recentes do pacote para os {VERSION} espaços reservados


em NuGet.org:

Microsoft.Build.Framework
Microsoft.Build.Utilities.Core

Para criar a tarefa MSBuild, crie uma classe C# pública estendendo


Microsoft.Build.Utilities.Task (não System.Threading.Tasks.Task) e declare três
propriedades:

PublishBlazorBootStaticWebAsset : a lista de arquivos a serem publicados para o

Blazor aplicativo.
BundlePath : o caminho em que o pacote é gravado.

Extension : as novas Extensões de Publicação a serem incluídas no build.


A classe de exemplo BundleBlazorAssets a seguir é um ponto de partida para
personalização adicional:

Execute No método, o pacote é criado com base nos três tipos de arquivo a

seguir:
Arquivos JavaScript ( dotnet.js )
Arquivos WASM ( dotnet.wasm )
DLLs do aplicativo ( *.dll )
Um multipart/form-data pacote é criado. Cada arquivo é adicionado ao pacote
com suas respectivas descrições por meio do cabeçalho Content-Disposition e
do cabeçalho Content-Type .
Depois que o pacote é criado, o pacote é gravado em um arquivo.
O build está configurado para a extensão. O código a seguir cria um item de
extensão e o adiciona à Extension propriedade. Cada item de extensão contém
três partes de dados:
O caminho para o arquivo de extensão.
O caminho da URL em relação à raiz do Blazor WebAssembly aplicativo.
O nome da extensão, que agrupa os arquivos produzidos por uma determinada
extensão.

Depois de atingir as metas anteriores, a tarefa MSBuild é criada para personalizar a


saída de Blazor publicação. Blazor cuida da coleta das extensões e garantir que as
extensões sejam copiadas para o local correto na pasta de saída de publicação (por
exemplo, bin\Release\net6.0\publish ). As mesmas otimizações (por exemplo,
compactação) são aplicadas aos arquivos JavaScript, WASM e DLL, conforme Blazor se
aplica a outros arquivos.

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/BundleBlazorAsse

ts.cs :

C#

using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks
{
public class BundleBlazorAssets : Task
{
[Required]
public ITaskItem[]? PublishBlazorBootStaticWebAsset { get; set; }
[Required]
public string? BundlePath { get; set; }

[Output]
public ITaskItem[]? Extension { get; set; }

public override bool Execute()


{
var bundle = new MultipartFormDataContent(
"--0a7e8441d64b4bf89086b85e59523b7d");

foreach (var asset in PublishBlazorBootStaticWebAsset)


{
var name =
Path.GetFileName(asset.GetMetadata("RelativePath"));
var fileContents = File.OpenRead(asset.ItemSpec);
var content = new StreamContent(fileContents);
var disposition = new ContentDispositionHeaderValue("form-
data");
disposition.Name = name;
disposition.FileName = name;
content.Headers.ContentDisposition = disposition;
var contentType = Path.GetExtension(name) switch
{
".js" => "text/javascript",
".wasm" => "application/wasm",
_ => "application/octet-stream"
};
content.Headers.ContentType =
MediaTypeHeaderValue.Parse(contentType);
bundle.Add(content);
}

using (var output = File.Open(BundlePath,


FileMode.OpenOrCreate))
{
output.SetLength(0);

bundle.CopyToAsync(output).ConfigureAwait(false).GetAwaiter()
.GetResult();
output.Flush(true);
}

var bundleItem = new TaskItem(BundlePath);


bundleItem.SetMetadata("RelativePath", "app.bundle");
bundleItem.SetMetadata("ExtensionName", "multipart");

Extension = new ITaskItem[] { bundleItem };

return true;
}
}
}
Criar um pacote NuGet para transformar
automaticamente a saída de publicação
Gere um pacote NuGet com destinos do MSBuild que são incluídos automaticamente
quando o pacote é referenciado:

Crie um projetoRazor de RCL (biblioteca de classes).


Crie um arquivo de destinos seguindo convenções do NuGet para importar
automaticamente o pacote no consumo de projetos. Por exemplo, crie
build\net6.0\{PACKAGE ID}.targets , onde {PACKAGE ID} está o identificador de
pacote do pacote.
Colete a saída da biblioteca de classes que contém a tarefa MSBuild e confirme se
a saída está empacotada no local certo.
Adicione o código MSBuild necessário para anexar ao Blazor pipeline e invocar a
tarefa MSBuild para gerar o pacote.

A abordagem descrita nesta seção usa apenas o pacote para fornecer destinos e
conteúdo, que é diferente da maioria dos pacotes em que o pacote inclui uma DLL de
biblioteca.

2 Aviso

O pacote de exemplo descrito nesta seção demonstra como personalizar o


processo de publicação Blazor . O pacote NuGet de exemplo é usado apenas
como uma demonstração local. Não há suporte para o uso desse pacote em
produção.

7 Observação

O pacote NuGet para os exemplos neste artigo tem o nome do pacote fornecido
pela Microsoft Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle .
Para obter diretrizes sobre como nomear e produzir seu próprio pacote NuGet,
consulte os seguintes artigos do NuGet:

Melhoras práticas de criação de pacote


Reserva de prefixo da ID do pacote

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/Microsoft.AspNetCore.Co
mponents.WebAssembly.MultipartBundle.csproj :
XML

<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<NoWarn>NU5100</NoWarn>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Sample demonstration package showing how to customize the Blazor
publish
process. Using this package in production is not supported!
</Description>
<IsPackable>true</IsPackable>
<IsShipping>true</IsShipping>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>

<ItemGroup>
<None Update="build\**"
Pack="true"
PackagePath="%(Identity)" />
<Content Include="_._"
Pack="true"
PackagePath="lib\net6.0\_._" />
</ItemGroup>

<Target Name="GetTasksOutputDlls"
BeforeTargets="CoreCompile">
<MSBuild
Projects="..\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tas
ks\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.csproj"
Targets="Publish;PublishItemsOutputGroup"
Properties="Configuration=Release">
<Output TaskParameter="TargetOutputs"
ItemName="_TasksProjectOutputs" />
</MSBuild>
<ItemGroup>
<Content Include="@(_TasksProjectOutputs)"
Condition="'%(_TasksProjectOutputs.Extension)' == '.dll'"
Pack="true"
PackagePath="tasks\%(_TasksProjectOutputs.TargetPath)"
KeepMetadata="Pack;PackagePath" />
</ItemGroup>
</Target>

</Project>

7 Observação
A <NoWarn>NU5100</NoWarn> propriedade no exemplo anterior suprime o aviso sobre
os assemblies colocados na tasks pasta. Para obter mais informações, consulte
NuGet Warning NU5100.

Adicione um .targets arquivo para conectar a tarefa MSBuild ao pipeline de build.


Neste arquivo, as seguintes metas são cumpridas:

Importe a tarefa para o processo de build. Observe que o caminho para a DLL é
relativo ao local final do arquivo no pacote.
A ComputeBlazorExtensionsDependsOn propriedade anexa o destino personalizado
ao Blazor WebAssembly pipeline.
Capture a Extension propriedade na saída da tarefa e adicione-a para
BlazorPublishExtension informar Blazor sobre a extensão. Invocar a tarefa no

destino produz o pacote. A lista de arquivos publicados é fornecida pelo Blazor


WebAssembly pipeline no PublishBlazorBootStaticWebAsset grupo de itens. O
caminho do pacote é definido usando o IntermediateOutputPath (normalmente
dentro da obj pasta). Em última análise, o pacote é copiado automaticamente
para o local correto na pasta de saída de publicação (por exemplo,
bin\Release\net6.0\publish ).

Quando o pacote é referenciado, ele gera um pacote dos arquivos durante a Blazor
publicação.

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/build/net6.0/Microsoft.

AspNetCore.Components.WebAssembly.MultipartBundle.targets :

XML

<Project>
<UsingTask

TaskName="Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.
BundleBlazorAssets"

AssemblyFile="$(MSBuildThisProjectFileDirectory)..\..\tasks\Microsoft.AspNet
Core.Components.WebAssembly.MultipartBundle.Tasks.dll" />

<PropertyGroup>
<ComputeBlazorExtensionsDependsOn>
$(ComputeBlazorExtensionsDependsOn);_BundleBlazorDlls
</ComputeBlazorExtensionsDependsOn>
</PropertyGroup>

<Target Name="_BundleBlazorDlls">
<BundleBlazorAssets
PublishBlazorBootStaticWebAsset="@(PublishBlazorBootStaticWebAsset)"
BundlePath="$(IntermediateOutputPath)bundle.multipart">
<Output TaskParameter="Extension"
ItemName="BlazorPublishExtension"/>
</BundleBlazorAssets>
</Target>

</Project>

Inicializar automaticamente Blazor do pacote


O pacote NuGet aproveita inicializadores JavaScript (JS) para inicializar automaticamente
um Blazor WebAssembly aplicativo do pacote em vez de usar arquivos DLL individuais.
JS Inicializadores são usados para alterar o carregador de Blazorrecursos de inicialização
e usar o pacote.

Para criar um JS inicializador, adicione um JS arquivo com o nome {NAME}.lib.module.js


à wwwroot pasta do projeto de pacote, onde o {NAME} espaço reservado é o
identificador do pacote. Por exemplo, o arquivo para o pacote da Microsoft é nomeado
Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js . As

funções beforeStart exportadas e afterStarted o carregamento do identificador.

Os JS inicializadores:

Detecte se a Extensão de Publicação está disponível pela


extensions.multipart verificação, que é o nome da extensão ( ExtensionName )

fornecido na tarefa Criar um MSBuild para personalizar a lista de arquivos


publicados e definir a nova seção de extensões .
Baixe o pacote e analise o conteúdo em um mapa de recursos usando URLs de
objeto geradas.
Atualize o carregador de recursos de inicialização (options.loadBootResource) com
uma função personalizada que resolve os recursos usando as URLs do objeto.
Depois que o aplicativo for iniciado, revogue as URLs do objeto para liberar
memória na afterStarted função.

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNe
tCore.Components.WebAssembly.MultipartBundle.lib.module.js :

JavaScript

const resources = new Map();

export async function beforeStart(options, extensions) {


if (!extensions || !extensions.multipart) {
return;
}

try {
const integrity = extensions.multipart['app.bundle'];
const bundleResponse =
await fetch('app.bundle', { integrity: integrity, cache: 'no-cache'
});
const bundleFromData = await bundleResponse.formData();
for (let value of bundleFromData.values()) {
resources.set(value, URL.createObjectURL(value));
}
options.loadBootResource = function (type, name, defaultUri, integrity)
{
return resources.get(name) ?? null;
}
} catch (error) {
console.log(error);
}
}

export async function afterStarted(blazor) {


for (const [_, url] of resources) {
URL.revokeObjectURL(url);
}
}

Servir o pacote do aplicativo do servidor host


Devido a restrições de segurança, ASP.NET Core não serve o app.bundle arquivo por
padrão. Um auxiliar de processamento de solicitação é necessário para atender ao
arquivo quando ele é solicitado pelos clientes.

7 Observação

Como as mesmas otimizações são aplicadas de forma transparente às Extensões de


Publicação aplicadas aos arquivos do aplicativo, os arquivos de ativos compactados
e app.bundle.br compactados app.bundle.gz são produzidos automaticamente na
publicação.

Coloque o código C# no Program.cs projeto imediatamente antes da linha que define o


arquivo de fallback como index.html ( app.MapFallbackToFile("index.html"); ) para
responder a uma solicitação para o arquivo de pacote (por exemplo, app.bundle ):Server

C#
app.MapGet("app.bundle", (HttpContext context) =>
{
string? contentEncoding = null;
var contentType =
"multipart/form-data; boundary=\"-
-0a7e8441d64b4bf89086b85e59523b7d\"";
var fileName = "app.bundle";

var acceptEncodings = context.Request.Headers.AcceptEncoding;

if (Microsoft.Net.Http.Headers.StringWithQualityHeaderValue
.StringWithQualityHeaderValue
.TryParseList(acceptEncodings, out var encodings))
{
if (encodings.Any(e => e.Value == "br"))
{
contentEncoding = "br";
fileName += ".br";
}
else if (encodings.Any(e => e.Value == "gzip"))
{
contentEncoding = "gzip";
fileName += ".gz";
}
}

if (contentEncoding != null)
{
context.Response.Headers.ContentEncoding = contentEncoding;
}

return Results.File(
app.Environment.WebRootFileProvider.GetFileInfo(fileName)
.CreateReadStream(), contentType);
});

O tipo de conteúdo corresponde ao tipo definido anteriormente na tarefa de build. O


ponto de extremidade verifica as codificações de conteúdo aceitas pelo navegador e
serve o arquivo ideal, Brotli ( .br ) ou Gzip ( .gz ).
Vários aplicativos de ASP.NET Core
Blazor WebAssembly hospedados
Artigo • 28/11/2022 • 10 minutos para o fim da leitura

Este artigo explica como configurar um aplicativo hospedado Blazor WebAssembly para
hospedar vários Blazor WebAssembly aplicativos.

Configuração
As soluções hospedadas Blazor podem atender a vários Blazor WebAssembly
aplicativos.

No exemplo a seguir:

O nome do projeto do aplicativo hospedado Blazor WebAssembly está


MultipleBlazorApps em uma pasta chamada MultipleBlazorApps .
Os três projetos na solução antes de um segundo aplicativo cliente ser adicionado
estão MultipleBlazorApps.Client na Client pasta, MultipleBlazorApps.Server na
Server pasta e MultipleBlazorApps.Shared na Shared pasta.
O aplicativo cliente inicial (primeiro) é o projeto cliente padrão da solução criada
com base no Blazor WebAssembly modelo de projeto. O primeiro aplicativo cliente
é acessível em um navegador na porta 5001 ou com uma série de firstapp.com .
Um segundo aplicativo cliente é adicionado à solução,
MultipleBlazorApps.SecondClient em uma pasta chamada SecondClient . O
segundo aplicativo cliente está acessível em um navegador na porta 5002 ou com
uma série de secondapp.com .
Opcionalmente, o projeto do servidor ( MultipleBlazorApps.Server ) pode servir
páginas ou exibições como um aplicativo MVC ou páginas formais Razor .

O exemplo mostrado nesta seção requer configuração adicional para:

Acessando os aplicativos diretamente nos domínios firstapp.com de host de


exemplo e secondapp.com .
Certificados para os aplicativos cliente para habilitar a segurança TLS/HTTPS.
Configurando o aplicativo de servidor como um Razor aplicativo Pages para os
seguintes recursos:
Integração de Razor componentes a páginas ou exibições.
Componentes de pré-geração Razor .
As configurações anteriores estão além do escopo dessa demonstração. Para saber
mais, consulte os recursos a seguir:

Hospedar e implantar artigos


Impor HTTPS no ASP.NET Core
Pré-renderizar e integrar componentes Razor do ASP.NET Core

Use uma solução hospedada Blazor WebAssembly existente ou crie uma nova solução
hospedada Blazor WebAssembly a Blazor WebAssembly partir do modelo de projeto
passando a opção -ho|--hosted se estiver usando a CLI do .NET ou selecionando a caixa
de seleção hospedada ASP.NET Core no Visual Studio ou Visual Studio para Mac
quando o projeto for criado no IDE.

Use uma pasta para a solução nomeada MultipleBlazorApps e nomeie o projeto


MultipleBlazorApps .

Projetos iniciais na solução e suas pastas:

MultipleBlazorApps.Client é um Blazor WebAssembly aplicativo cliente na Client


pasta.
MultipleBlazorApps.Server é um aplicativo de servidor ASP.NET Core que serve

Blazor WebAssembly aplicativos) na Server pasta. Opcionalmente, o aplicativo de


servidor também pode servir páginas ou exibições, como um aplicativo MVC ou
páginas tradicionais Razor .
MultipleBlazorApps.Shared é um projeto de recursos compartilhados para os

projetos de cliente e servidor na Shared pasta.

No arquivo de projeto do aplicativo cliente ( MultipleBlazorApps.Client.csproj ),


adicione uma <StaticWebAssetBasePath> propriedade a um <PropertyGroup> com um
valor de FirstApp definir o caminho base para os ativos estáticos do projeto:

XML

<StaticWebAssetBasePath>FirstApp</StaticWebAssetBasePath>

7 Observação

A demonstração nesta seção usa nomes de caminho de FirstApp ativo da Web,


SecondApp mas esses nomes específicos são apenas para fins de demonstração.

Todos os segmentos de caminho base que distinguem os aplicativos cliente são


aceitáveis, como App1 App2 /,/ Client1 Client2 , 1 / 2 ou qualquer esquema de
nomenclatura semelhante. Esses segmentos de caminho base são usados
internamente para rotear solicitações e atender respostas e não são vistos na barra
de endereços de um navegador.

Adicione um segundo aplicativo cliente à solução. Adicione o projeto como um


aplicativo autônomo Blazor WebAssembly . Para criar um aplicativo autônomoBlazor
WebAssembly, não passe a opção -ho|--hosted se estiver usando a CLI do .NET ou não
use a caixa de seleção ASP.NET Core hospedada se estiver usando o Visual Studio:

Nomeie o projeto MultipleBlazorApps.SecondClient e coloque o aplicativo em


uma pasta chamada SecondClient .
A pasta de solução criada a partir do modelo de projeto contém o seguinte
arquivo de solução e pastas depois que a SecondClient pasta é adicionada:
Client (pasta)
SecondClient (pasta)
Server (pasta)
Shared (pasta)
MultipleBlazorApps.sln (arquivo)

MultipleBlazorApps.SecondClient No arquivo de projeto do aplicativo

( MultipleBlazorApps.SecondClient.csproj ):

Adicione uma <StaticWebAssetBasePath> propriedade a um <PropertyGroup> com


um valor de SecondApp :

XML

<StaticWebAssetBasePath>SecondApp</StaticWebAssetBasePath>

Adicione uma referência de projeto para o MultipleBlazorApps.Shared projeto a


um <ItemGroup> :

XML

<ItemGroup>
<ProjectReference
Include="..\Shared\MultipleBlazorApps.Shared.csproj" />
</ItemGroup>

No arquivo de projeto do aplicativo de servidor


( Server/MultipleBlazorApps.Server.csproj ), crie uma referência de projeto para o
aplicativo cliente adicionado MultipleBlazorApps.SecondClient em um <ItemGroup> :
XML

<ProjectReference
Include="..\SecondClient\MultipleBlazorApps.SecondClient.csproj" />

No arquivo do aplicativo de Properties/launchSettings.json servidor, configure o


applicationUrl Kestrel perfil ( MultipleBlazorApps.Server ) para acessar os aplicativos

cliente em duas portas.

7 Observação

O uso de portas nesta demonstração permite o acesso aos projetos cliente em um


navegador local sem a necessidade de configurar um ambiente de hospedagem
local para que os navegadores da Web possam acessar os aplicativos cliente por
meio das configurações firstapp.com do host e secondapp.com . Em cenários de
produção, uma configuração típica é usar subdomínios para distinguir os
aplicativos cliente.

Por exemplo:

As portas são removidas da configuração dessa demonstração.


Os hosts são alterados para usar subdomínios, como www.contoso.com para
visitantes do site e admin.contoso.com para administradores.
Hosts adicionais podem ser incluídos para aplicativos cliente adicionais, e pelo
menos mais um host será necessário se o aplicativo servidor também for um
Razor aplicativo Pages ou MVC que atenda páginas ou exibições.

Se você planeja fornecer páginas ou exibições do aplicativo de servidor, use a seguinte


applicationUrl configuração no arquivo, que Properties/launchSettings.json permite

o seguinte acesso:

O Razor aplicativo Pages ou MVC responde a solicitações na porta 5000.


As respostas às solicitações para o primeiro cliente estão na porta 5001.
As respostas às solicitações para o segundo cliente estão na porta 5002.

JSON

"applicationUrl":
"https://localhost:5000;https://localhost:5001;https://localhost:5002",

Se você não planeja que o aplicativo de servidor atenda a páginas ou exibições e atenda
somente aos Blazor WebAssembly aplicativos cliente, use a seguinte configuração, que
permite o seguinte acesso:

O primeiro aplicativo cliente responde na porta 5001.


O segundo aplicativo cliente responde na porta 5002.

JSON

"applicationUrl": "https://localhost:5001;https://localhost:5002",

No arquivo do aplicativo de Program.cs servidor, remova o código a seguir, que aparece


após a chamada para UseHttpsRedirection:

Se você planeja fornecer páginas ou exibições do aplicativo de servidor, exclua a


seguinte linha de código:

diff

- app.UseBlazorFrameworkFiles();

Se você planeja que o aplicativo de servidor atenda apenas aos Blazor


WebAssembly aplicativos cliente, exclua o seguinte código:

diff

- app.UseBlazorFrameworkFiles();
- app.UseStaticFiles();

- app.UseRouting();

- app.MapRazorPages();
- app.MapControllers();
- app.MapFallbackToFile("index.html");

Adicione middleware que mapeia solicitações para os aplicativos cliente. O


exemplo a seguir configura o middleware a ser executado quando:
A porta de solicitação é 5001 para o primeiro aplicativo cliente ou 5002 para o
segundo aplicativo cliente.
O host de solicitação é firstapp.com para o primeiro aplicativo cliente ou
secondapp.com para o segundo aplicativo cliente.

7 Observação

O uso dos hosts ( firstapp.com / secondapp.com ) em um sistema local com um


navegador local requer uma configuração adicional além do escopo deste
artigo. Para testes locais desse cenário, recomendamos o uso de portas.
Aplicativos de produção típicos são configurados para usar subdomínios,
como www.contoso.com para visitantes do site e admin.contoso.com para
administradores. Com a configuração adequada de servidor e DNS, que está
além do escopo deste artigo e depende das tecnologias usadas, o aplicativo
responde a solicitações em quaisquer hosts nomeados no código a seguir.

Coloque o seguinte código em que o código foi removido anteriormente no


Program.cs arquivo:

C#

app.MapWhen(ctx => ctx.Request.Host.Port == 5001 ||


ctx.Request.Host.Equals("firstapp.com"), first =>
{
first.Use((ctx, nxt) =>
{
ctx.Request.Path = "/FirstApp" + ctx.Request.Path;
return nxt();
});

first.UseBlazorFrameworkFiles("/FirstApp");
first.UseStaticFiles();
first.UseStaticFiles("/FirstApp");
first.UseRouting();

first.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToFile("/FirstApp/{*path:nonfile}",
"FirstApp/index.html");
});
});

app.MapWhen(ctx => ctx.Request.Host.Port == 5002 ||


ctx.Request.Host.Equals("secondapp.com"), second =>
{
second.Use((ctx, nxt) =>
{
ctx.Request.Path = "/SecondApp" + ctx.Request.Path;
return nxt();
});

second.UseBlazorFrameworkFiles("/SecondApp");
second.UseStaticFiles();
second.UseStaticFiles("/SecondApp");
second.UseRouting();

second.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToFile("/SecondApp/{*path:nonfile}",
"SecondApp/index.html");
});
});

Para obter mais informações sobreUseStaticFiles, consulte ASP.NET Core Blazor arquivos
estáticos.

Para obter mais informações sobre UseBlazorFrameworkFiles e MapFallbackToFile ,


confira os seguintes recursos:

Microsoft.AspNetCore.Builder.ComponentsWebAssemblyApplicationBuilderExtensi
ons.UseBlazorFrameworkFiles (origem de referência )
Microsoft.AspNetCore.Builder.StaticFilesEndpointRouteBuilderExtensions.MapFallba
ckToFile (origem de referência )

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Se você planeja servir páginas do aplicativo de servidor, adicione uma Index Razor
página à Pages pasta do aplicativo de servidor:

Pages/Index.cshtml :

CSHTML

@page
@model MultipleBlazorApps.Server.Pages.IndexModel
@{
ViewData["Title"] = "Home page";
}

<div>
<h1>Welcome</h1>
<p>Hello from Razor Pages!</p>
</div>

Pages/Index.cshtml.cs :
C#

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

namespace MultipleBlazorApps.Server.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}

7 Observação

Se o aplicativo exigir ativos de Páginas adicionais Razor , como um layout, estilos,


scripts e importações, obtenha-os de um aplicativo criado a partir do Razor modelo
de projeto Pages. Para obter mais informações, consulte Introdução às Razor
Páginas no ASP.NET Core.

Se você planeja fornecer exibições de MVC do aplicativo de servidor, adicione um modo


de exibição Index e um Home controlador:

Views/Home/Index.cshtml :

CSHTML

@{
ViewData["Title"] = "Home Page";
}

<div>
<h1>Welcome</h1>
<p>Hello from MVC!</p>
</div>

Controllers/HomeController.cs :

C#

using Microsoft.AspNetCore.Mvc;

namespace MultipleBlazorApps.Server.Controllers;

public class HomeController : Controller


{
public IActionResult Index()
{
return View();
}
}

7 Observação

Se o aplicativo exigir ativos MVC adicionais, como um layout, estilos, scripts e


importações, obtenha-os de um aplicativo criado com base no modelo de projeto
do MVC. Para obter mais informações, consulte Introdução ao ASP.NET Core MVC.

O middleware adicionado ao pipeline de processamento de solicitação do aplicativo de


servidor modifica anteriormente as solicitações de entrada para
/WeatherForecast /FirstApp/WeatherForecast /SecondApp/WeatherForecast ou

dependendo da porta (5001/5002) ou domínio (). firstapp.com / secondapp.com Portanto,


as rotas do controlador que retornam dados meteorológicos do aplicativo do servidor
para os aplicativos cliente exigem uma modificação.

No controlador de previsão do tempo do aplicativo de servidor


( Controllers/WeatherForecastController.cs ), substitua a rota existente ( [Route("
[controller]")] ) pelas WeatherForecastController rotas a seguir, que levam em conta

os caminhos base dos aplicativos cliente adicionados pelo middleware


( FirstApp / SecondApp ):

C#

[Route("FirstApp/[controller]")]
[Route("SecondApp/[controller]")]

Execute o MultipleBlazorApps.Server projeto:

Acesse o aplicativo cliente inicial em https://localhost:5001 .


Acesse o aplicativo cliente adicionado em https://localhost:5002 .
Se o aplicativo de servidor estiver configurado para servir páginas ou exibições,
acesse a página ou exibição Index em https://localhost:5000 .

Para obter mais informações sobre como usar os Razor componentes de qualquer um
dos aplicativos cliente em páginas ou exibições do aplicativo de servidor, consulte
Prerender e integre ASP.NET Core Razor componentes.
Ativos estáticos
Quando um ativo estiver na pasta de um aplicativo cliente, forneça o caminho de
wwwroot solicitação de ativo estático em componentes:

HTML

<img alt="..." src="/{PATH AND FILE NAME}" />

O espaço reservado {PATH AND FILE NAME} é o caminho e o nome do arquivo em


wwwroot .

Por exemplo, a origem de uma imagem do Jeep ( jeep-yj.png ) na vehicle pasta de


wwwroot :

HTML

<img alt="Jeep Wrangler YJ" src="/vehicle/jeep-yj.png" />

Suporte à RCL (biblioteca de classes) Razor


Adicione a Razor RCL (biblioteca de classes) à solução como um novo projeto:

Clique com o botão direito do mouse na solução em Gerenciador de Soluções e


escolha Adicionar>Novo Projeto.
Use o modelo de Razor projeto da Biblioteca de Classes para criar o projeto. Os
exemplos nesta seção usam o nome ComponentLibrary do projeto, que também é o
nome do assembly da RCL. Não selecione as páginas de suporte e a caixa de
seleção de exibições.

Para cada aplicativo cliente hospedadoBlazor WebAssembly, crie uma referência de


projeto para o projeto RCL clicando com o botão direito do mouse em cada projeto
cliente em Gerenciador de Soluções e selecionando Adicionar>Referência de Projeto.

Use componentes da RCL nos aplicativos cliente com qualquer uma das seguintes
abordagens:

Coloque uma @using diretiva na parte superior do componente para o namespace


da RCL e adicione Razor sintaxe para o componente. O exemplo a seguir é para
uma RCL com o nome ComponentLibrary do assembly:

razor
@using ComponentLibrary

...

<Component1 />

Forneça o namespace da RCL junto com a Razor sintaxe do componente. Essa


abordagem não requer uma @using diretiva na parte superior do arquivo de
componente. O exemplo a seguir é para uma RCL com o nome
ComponentLibrary do assembly:

razor

<ComponentLibrary.Component1 />

7 Observação

Uma @using diretiva também pode ser colocada no arquivo de _Import.razor


cada aplicativo cliente, o que torna o namespace da RCL globalmente disponível
para componentes nesse projeto.

Adicione manualmente a folha de estilos empacotada da RCL ao <head> conteúdo de


wwwroot/index.html cada aplicativo cliente que consome a RCL. O exemplo a seguir é
para uma RCL com o nome ComponentLibrary do assembly:

HTML

<link href="_content/ComponentLibrary/ComponentLibrary.bundle.scp.css"
rel="stylesheet" />

Quando qualquer outro ativo estático estiver na wwwroot pasta de uma RCL, faça
referência ao ativo estático em um aplicativo cliente de acordo com as diretrizes na
interface do usuário reutilizável Razor em bibliotecas de classes com ASP.NET Core:

HTML

<img alt="..." src="_content/{PACKAGE ID}/{PATH AND FILE NAME}" />

O {PACKAGE ID} espaço reservado é a ID do pacote da RCL. A ID do pacote terá como


valor padrão o nome do assembly do projeto, se <PackageId> não for especificado no
arquivo de projeto. O {PATH AND FILE NAME} espaço reservado é o nome do caminho e
do arquivo em wwwroot .
O exemplo a seguir mostra a origem de uma imagem do Jeep ( jeep-yj.png ) na vehicle
pasta da RCL. wwwroot O exemplo a seguir é para uma RCL com o nome
ComponentLibrary do assembly:

HTML

<img alt="Jeep Wrangler YJ" src="_content/ComponentLibrary/vehicle/jeep-


yj.png" />

Recursos adicionais
Consumir componentes do Razor de uma Razor biblioteca de classes (RCL)
Interface do usuário do Razor reutilizável em bibliotecas de classes com o ASP.NET
Core
Blazor ASP.NET Core isolamento do CSS
Blazor Server ASP.NET Core com o Entity
Framework Core (EF Core)
Artigo • 09/11/2022 • 24 minutos para o fim da leitura

Este artigo explica como usar o Entity Framework Core (EF Core) em Blazor Server
aplicativos.

Blazor Server é uma estrutura de aplicativo com estado. O aplicativo mantém uma
conexão contínua com o servidor e o estado do usuário é mantido na memória do
servidor em um circuito. Um exemplo de estado do usuário são dados mantidos em
instâncias de serviço di (injeção de dependência) que têm escopo para o circuito. O
modelo de aplicativo exclusivo que Blazor Server fornece requer uma abordagem
especial para usar o Entity Framework Core.

7 Observação

Este artigo aborda EF Core em Blazor Server aplicativos. Blazor WebAssembly os


aplicativos são executados em uma área restrita webAssembly que impede a
maioria das conexões de banco de dados diretas. A execução EF Core no Blazor
WebAssembly está além do escopo deste artigo.

Aplicativo de exemplo
O aplicativo de exemplo foi criado como uma referência para Blazor Server aplicativos
que usam EF Core. O aplicativo de exemplo inclui uma grade com operações de
classificação e filtragem, exclusão, adição e atualização. O exemplo demonstra o uso de
EF Core para lidar com a simultaneidade otimista.

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

O exemplo usa um banco de dados SQLite local para que ele possa ser usado em
qualquer plataforma. O exemplo também configura o log de banco de dados para
mostrar as consultas SQL geradas. Isso está configurado em
appsettings.Development.json :

JSON

{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}

Os componentes de grade, adição e exibição usam o padrão "contexto por operação",


em que um contexto é criado para cada operação. O componente de edição usa o
padrão "contexto por componente", em que um contexto é criado para cada
componente.

7 Observação

Alguns dos exemplos de código neste tópico exigem namespaces e serviços que
não são mostrados. Para inspecionar o código totalmente funcional, incluindo as
diretivas e @inject necessárias @using para Razor obter exemplos, consulte o
aplicativo de exemplo .

Acesso ao banco de dados


EF Core depende de um DbContext como os meios para configurar o acesso ao banco
de dados e atuar como uma unidade de trabalho . EF Corefornece a AddDbContext
extensão para ASP.NET Core aplicativos que registram o contexto como um serviço com
escopo por padrão. Em Blazor Server aplicativos, os registros de serviço com escopo
podem ser problemáticos porque a instância é compartilhada entre componentes
dentro do circuito do usuário. DbContext não é thread-safe e não foi projetado para uso
simultâneo. Os tempos de vida existentes são inadequados por estes motivos:

O singleton compartilha o estado entre todos os usuários do aplicativo e leva a


um uso simultâneo inadequado.
O escopo (o padrão) representa um problema semelhante entre componentes
para o mesmo usuário.
Resultados transitórios em uma nova instância por solicitação; mas como os
componentes podem ser de longa duração, isso resulta em um contexto de vida
mais longa do que pode ser pretendido.

As recomendações a seguir foram projetadas para fornecer uma abordagem consistente


para usar EF Core em Blazor Server aplicativos.
Por padrão, considere usar um contexto por operação. O contexto foi projetado
para instanciação rápida e de baixa sobrecarga:

C#

using var context = new MyContext();

return await context.MyEntities.ToListAsync();

Use um sinalizador para evitar várias operações simultâneas:

C#

if (Loading)
{
return;
}

try
{
Loading = true;

...
}
finally
{
Loading = false;
}

Faça operações após a Loading = true; linha no try bloco.

Carregar lógica não requer bloqueio de registros de banco de dados porque a


segurança do thread não é uma preocupação. A lógica de carregamento é usada
para desabilitar controles de interface do usuário para que os usuários não
selecionem botões ou atualizem campos inadvertidamente enquanto os dados são
buscados.

Se houver alguma chance de que vários threads possam acessar o mesmo bloco
de código, injete uma fábrica e crie uma nova instância por operação. Caso
contrário, injetar e usar o contexto geralmente é suficiente.

Para operações mais longas que aproveitam o controle de EF Corealteração ou


simultaneidade do , o escopo do contexto para o tempo de vida do componente.

A maneira mais rápida de criar uma nova DbContext instância é usando new para criar
uma nova instância. No entanto, há cenários que exigem a resolução de dependências
adicionais:
Usar DbContextOptions para configurar o contexto.
Usando uma cadeia de conexão por DbContext, como quando você usa o modelo
do Identity ASP.NET Core. Para obter mais informações, confira Multilocação (EF
Core documentação).

A abordagem recomendada para criar um novo DbContext com dependências é usar


uma fábrica. EF Core 5.0 ou posterior fornece uma fábrica interna para criar novos
contextos.

O exemplo a seguir configura o SQLite e habilita o log de dados. O código usa um


método de extensão ( AddDbContextFactory ) para configurar a fábrica de banco de dados
para DI e fornecer opções padrão:

C#

builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));

A fábrica é injetada em componentes e usada para criar novas DbContext instâncias.

No Pages/Index.razor aplicativo de exemplo , IDbContextFactory<ContactContext> é


injetado no componente :

razor

@inject IDbContextFactory<ContactContext> DbFactory

Um DbContext é criado usando a fábrica ( DbFactory ) para excluir um contato no


DeleteContactAsync método :

C#

private async Task DeleteContactAsync()


{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;

if (Wrapper is not null && context.Contacts is not null)


{
var contact = await context.Contacts
.FirstAsync(c => c.Id == Wrapper.DeleteRequestId);

if (contact is not null)


{
context.Contacts?.Remove(contact);
await context.SaveChangesAsync();
}
}

Filters.Loading = false;

await ReloadAsync();
}

7 Observação

Filters é um injetado IContactFilters e Wrapper é uma referência de


componente para o GridWrapper componente. Consulte o Index componente
( Pages/Index.razor ) no aplicativo de exemplo .

Escopo para o tempo de vida do componente


Talvez você queira criar um DbContext que exista para o tempo de vida de um
componente. Isso permite que você o use como uma unidade de trabalho e aproveite
os recursos internos, como controle de alterações e resolução de simultaneidade. Você
pode usar a fábrica para criar um contexto e rastreá-lo durante o tempo de vida do
componente. Primeiro, implemente IDisposable e injete a fábrica conforme mostrado
em Pages/EditContact.razor :

razor

@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory

O aplicativo de exemplo garante que o contexto seja descartado quando o componente


for descartado:

C#

public void Dispose()


{
Context?.Dispose();
}

Por fim, OnInitializedAsync é substituído para criar um novo contexto. No aplicativo de


exemplo, OnInitializedAsync carrega o contato no mesmo método:

C#
protected override async Task OnInitializedAsync()
{
Busy = true;

try
{
Context = DbFactory.CreateDbContext();

if (Context is not null && Context.Contacts is not null)


{
var contact = await Context.Contacts.SingleOrDefaultAsync(c =>
c.Id == ContactId);

if (contact is not null)


{
Contact = contact;
}
}
}
finally
{
Busy = false;
}

await base.OnInitializedAsync();
}

Habilitar o registro em log de dados


confidenciais
EnableSensitiveDataLogging inclui dados do aplicativo em mensagens de exceção e log
de estrutura. Os dados registrados podem incluir os valores atribuídos às propriedades
de instâncias de entidade e valores de parâmetro para comandos enviados ao banco de
dados. Registrar dados com EnableSensitiveDataLogging é um risco de segurança, pois
podem expor senhas e outras PII (informações de identificação pessoal) quando
registram instruções SQL executadas no banco de dados.

Recomendamos habilitar EnableSensitiveDataLogging apenas para desenvolvimento e


teste:

C#

#if DEBUG
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
#else
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source=
{nameof(ContactContext.ContactsDb)}.db"));
#endif

Recursos adicionais
Documentação do EF Core
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Blazor ASP.NET Core cenários avançados
(construção de árvore de renderização)
Artigo • 28/11/2022 • 20 minutos para o fim da leitura

Este artigo descreve o cenário avançado para criar Blazor árvores de renderização
manualmente com RenderTreeBuilder.

2 Aviso

O uso de RenderTreeBuilder para criar componentes é um cenário avançado. Um


componente malformado (por exemplo, uma marca de marcação não revelada)
pode resultar em um comportamento indefinido. O comportamento indefinido
inclui renderização de conteúdo interrompida, perda de recursos do aplicativo e
segurança comprometida.

Criar manualmente uma árvore de renderização


( RenderTreeBuilder )
RenderTreeBuilder fornece métodos para manipular componentes e elementos,
incluindo a criação manual de componentes no código C#.

Considere o componente a seguir PetDetails , que pode ser renderizado manualmente


em outro componente.

Shared/PetDetails.razor :

razor

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

@code
{
[Parameter]
public string? PetDetailsQuote { get; set; }
}

No componente a seguir BuiltContent , o loop no CreateComponent método gera três


PetDetails componentes.
Em RenderTreeBuilder métodos com um número de sequência, os números de
sequência são números de linha do código-fonte. O Blazor algoritmo de diferença
depende dos números de sequência correspondentes a linhas distintas de código, não
invocações de chamadas distintas. Ao criar um componente com RenderTreeBuilder
métodos, codifique os argumentos para números de sequência. Usar um cálculo ou
contador para gerar o número de sequência pode levar a um desempenho ruim. Para
obter mais informações, consulte a seção Números de sequência relacionados a
números de linha de código e não à ordem de execução .

Pages/BuiltContent.razor :

razor

@page "/built-content"

<h1>Build a component</h1>

<div>
@CustomRender
</div>

<button @onclick="RenderComponent">
Create three Pet Details components
</button>

@code {
private RenderFragment? CustomRender { get; set; }

private RenderFragment CreateComponent() => builder =>


{
for (var i = 0; i < 3; i++)
{
builder.OpenComponent(0, typeof(PetDetails));
builder.AddAttribute(1, "PetDetailsQuote", "Someone's best
friend!");
builder.CloseComponent();
}
};

private void RenderComponent()


{
CustomRender = CreateComponent();
}
}

2 Aviso

Os tipos em Microsoft.AspNetCore.Components.RenderTree permitem o


processamento dos resultados das operações de renderização. Estes são detalhes
internos da implementação da Blazor estrutura. Esses tipos devem ser considerados
instáveis e sujeitos a alterações em versões futuras.

Números de sequência estão relacionados a números de


linha de código e não à ordem de execução
Razor arquivos de componente ( .razor ) são sempre compilados. A execução do código
compilado tem uma vantagem potencial em relação à interpretação de código porque a
etapa de compilação que produz o código compilado pode ser usada para injetar
informações que melhoram o desempenho do aplicativo em runtime.

Um exemplo importante dessas melhorias envolve números de sequência. Números de


sequência indicam para o runtime quais saídas vieram de quais linhas de código
distintas e ordenadas. O runtime usa essas informações para gerar diferenças de árvore
eficientes em tempo linear, o que é muito mais rápido do que normalmente é possível
para um algoritmo de comparação de árvore geral.

Considere o seguinte Razor arquivo de componente ( .razor ):

razor

@if (someFlag)
{
<text>First</text>
}

Second

O conteúdo de texto e marcação Razor anteriores é compilado em código C#


semelhante ao seguinte:

C#

if (someFlag)
{
builder.AddContent(0, "First");
}

builder.AddContent(1, "Second");

Quando o código é executado pela primeira vez e someFlag é true , o construtor recebe
a sequência na tabela a seguir.
Sequência Tipo Dados

0 Nó de texto Primeiro

1 Nó de texto Segundo

Imagine que someFlag se torna false e a marcação é renderizada novamente. Desta


vez, o construtor recebe a sequência na tabela a seguir.

Sequência Tipo Dados

1 Nó de texto Segundo

Quando o runtime executa uma comparação, ele vê que o item na sequência 0 foi
removido, portanto, ele gera o seguinte script de edição trivial com uma única etapa:

Remova o primeiro nó de texto.

O problema com a geração de números de sequência


programaticamente
Imagine, em vez disso, que você escreveu a seguinte lógica do construtor de árvore de
renderização:

C#

var seq = 0;

if (someFlag)
{
builder.AddContent(seq++, "First");
}

builder.AddContent(seq++, "Second");

A primeira saída é refletida na tabela a seguir.

Sequência Tipo Dados

0 Nó de texto Primeiro

1 Nó de texto Segundo

Esse resultado é idêntico ao caso anterior, portanto, não existem problemas negativos.
someFlag está false na segunda renderização e a saída é vista na tabela a seguir.
Sequência Tipo Dados

0 Nó de texto Segundo

Desta vez, o algoritmo diff vê que duas alterações ocorreram. O algoritmo gera o
seguinte script de edição:

Altere o valor do primeiro nó de texto para Second .


Remova o segundo nó de texto.

Gerar os números de sequência perdeu todas as informações úteis sobre onde os


if/else branches e loops estavam presentes no código original. Isso resulta em uma
comparação duas vezes maior do que antes.

Este é um exemplo trivial. Em casos mais realistas com estruturas complexas e


profundamente aninhadas e, especialmente, com loops, o custo de desempenho
geralmente é maior. Em vez de identificar imediatamente quais blocos de loop ou
branches foram inseridos ou removidos, o algoritmo diff deve se repetir profundamente
nas árvores de renderização. Isso geralmente resulta na criação de scripts de edição
mais longos porque o algoritmo diff é mal informado sobre como as estruturas antigas
e novas se relacionam entre si.

Diretrizes e conclusões
O desempenho do aplicativo sofrerá se os números de sequência forem gerados
dinamicamente.
A estrutura não pode criar seus próprios números de sequência automaticamente
em runtime porque as informações necessárias não existem, a menos que sejam
capturadas em tempo de compilação.
Não escreva blocos longos de lógica implementada RenderTreeBuilder
manualmente. Prefira .razor arquivos e permita que o compilador lide com os
números de sequência. Se não for possível evitar a lógica manual
RenderTreeBuilder , divida blocos longos de código em partes menores
encapsuladas em OpenRegion/CloseRegion chamadas. Cada região tem seu
próprio espaço separado de números de sequência, para que você possa reiniciar
de zero (ou qualquer outro número arbitrário) dentro de cada região.
Se os números de sequência forem codificados, o algoritmo diff exigirá apenas
que os números de sequência aumentem de valor. O valor inicial e as lacunas são
irrelevantes. Uma opção legítima é usar o número de linha de código como o
número de sequência ou começar do zero e aumentar em um ou centenas (ou
qualquer intervalo preferencial).
Blazor usa números de sequência, enquanto outras estruturas de interface do
usuário de diferença de árvore não os usam. A comparação é muito mais rápida
quando os números de sequência são usados e Blazor tem a vantagem de uma
etapa de compilação que lida com números de sequência automaticamente para
desenvolvedores criando .razor arquivos.
Usar o modelo de projeto Angular com
o ASP.NET Core
Artigo • 10/01/2023 • 12 minutos para o fim da leitura

O ASP.NET Core com Angular modelo de projeto fornece um ponto de partida


conveniente para aplicativos ASP.NET Core usando Angular e a CLI do Angular para
implementar uma interface do usuário (interface do usuário) avançada do lado do
cliente.

O modelo de projeto é equivalente à criação de um projeto ASP.NET Core para atuar


como uma API Web e um projeto da CLI Angular para atuar como uma interface do
usuário. Essa combinação de projetos oferece a conveniência de hospedar ambos os
projetos em um único projeto ASP.NET Core que pode ser criado e publicado como
uma única unidade.

O modelo de projeto não se destina à SSR (renderização do lado do servidor).

Criar um novo aplicativo


Crie um novo projeto de um prompt de comando usando o comando dotnet new
angular em um diretório vazio. Por exemplo, os seguintes comandos criam o aplicativo
em um my-new-app diretório e alternam para esse diretório:

CLI do .NET

dotnet new angular -o my-new-app


cd my-new-app

Execute o aplicativo do Visual Studio ou da CLI do .NET Core:

Visual Studio

Abra o arquivo gerado .csproj e execute o aplicativo normalmente a partir daí.

O processo de build restaura dependências npm na primeira execução, o que pode


levar vários minutos. Builds subsequentes são muito mais rápidos.

O modelo de projeto cria um aplicativo ASP.NET Core e um aplicativo do Angular. O


aplicativo ASP.NET Core destina-se a ser usado para acesso a dados, autorização e
outras questões do lado do servidor. O aplicativo Angular, que reside no ClientApp
subdiretório, destina-se a ser usado para todas as preocupações da interface do usuário.

Adicione páginas, imagens, estilos, módulos,


etc.
O ClientApp diretório contém um aplicativo padrão da CLI Angular. Veja a
documentação oficial do Angular para obter mais informações.

Há pequenas diferenças entre o aplicativo do Angular criado por este modelo e um


criado pela CLI do Angular propriamente dita (por meio de ng new ); no entanto, os
recursos do aplicativo são inalterados. O aplicativo criado pelo modelo contém um
layout com base em Bootstrap e um exemplo básico de roteamento.

Executar comandos ng
Em um prompt de comando, alterne para o ClientApp subdiretório:

Console

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 .

Instalar pacotes npm


Para instalar pacotes npm de terceiros, use um prompt de comando no ClientApp
subdiretório. Por exemplo:

Console
cd ClientApp
npm install <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. Ao contrário do build de desenvolvimento, o build de
produção não exige que Node.js sejam instalados no servidor (a menos que você tenha
habilitado a SSR (renderização do lado do servidor)).

Você pode usar os métodos padrão de implantação e hospedagem do ASP.NET Core.

Executar "ng serve" independentemente


O projeto está configurado para iniciar sua própria instância do servidor da CLI do
Angular em segundo plano quando o aplicativo ASP.NET Core é iniciado no modo de
desenvolvimento. Isso é conveniente, porque você não precisa executar um servidor
separado manualmente.

Há uma desvantagem nessa configuração padrão. Cada vez que você modificar seu
código C# e o aplicativo ASP.NET Core precisar ser reiniciado, o servidor da CLI do
Angular será reiniciado também. São necessários aproximadamente 10 segundos para
iniciar um backup. Se você estiver fazendo edições frequentes de código C# e não
quiser esperar a CLI do Angular reiniciar, execute o servidor da CLI do Angular
externamente, independentemente do processo do ASP.NET Core.

Para executar o servidor da CLI Angular externamente, alterne para o ClientApp


subdiretório em um prompt de comando e inicie o servidor de desenvolvimento da CLI
Angular:

Console
cd ClientApp
npm start

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.

Configurar o middleware proxy para SignalR


Para obter mais informações, consulte http-proxy-middleware .

Recursos adicionais
Introdução à autenticação para aplicativos de página única no ASP.NET Core
Usar React com ASP.NET Core
Artigo • 10/01/2023 • 8 minutos para o fim da leitura

O modelo de projeto ASP.NET Core com React fornece um ponto de partida


conveniente para aplicativos ASP.NET Core usando React e o CRA (Create React App )
para implementar uma interface do usuário avançada do lado do cliente.

O modelo de projeto é equivalente à criação de um projeto ASP.NET Core para atuar


como uma API Web e um projeto de React CRA para atuar como uma interface do
usuário. Essa combinação de projetos oferece a conveniência de hospedar ambos os
projetos em um único projeto ASP.NET Core que pode ser criado e publicado como
uma única unidade.

O modelo de projeto não se destina à renderização do lado do servidor (SSR). Para SSR
com React e Node.js, considere Next.js ou Razzle .

Criar um novo aplicativo


Crie um novo projeto de um prompt de comando usando o comando dotnet new react
em um diretório vazio. Por exemplo, os seguintes comandos criam o aplicativo em um
my-new-app diretório e alternam para esse diretório:

CLI do .NET

dotnet new react -o my-new-app


cd my-new-app

Execute o aplicativo do Visual Studio ou da CLI do .NET Core:

Visual Studio

Abra o arquivo gerado .csproj e execute o aplicativo normalmente a partir daí.

O processo de build restaura dependências npm na primeira execução, o que pode


levar vários minutos. Builds subsequentes são muito mais rápidos.

O modelo de projeto cria um aplicativo ASP.NET Core e um aplicativo do React. O


aplicativo ASP.NET Core destina-se a ser usado para acesso a dados, autorização e
outras questões do lado do servidor. O aplicativo React, que reside no ClientApp
subdiretório, destina-se a ser usado para todas as preocupações da interface do usuário.
Adicione páginas, imagens, estilos, módulos,
etc.
O ClientApp diretório é um aplicativo de React CRA padrão. Veja a documentação
oficial do CRA para obter mais informações.

Há pequenas diferenças entre o aplicativo do React criado por este modelo e um criado
pelo CRA propriamente dito; no entanto, os recursos do aplicativo são inalterados. O
aplicativo criado pelo modelo contém um layout com base em Bootstrap e um
exemplo básico de roteamento.

Instalar pacotes npm


Para instalar pacotes npm de terceiros, use um prompt de comando no ClientApp
subdiretório . Por exemplo:

Console

cd ClientApp
npm install <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.

Executar o servidor CRA independentemente


O projeto está configurado para iniciar sua própria instância do Development Server do
CRA em segundo plano quando o aplicativo ASP.NET Core é iniciado no modo de
desenvolvimento. Isso é conveniente, porque significa que você não precisa executar um
servidor separado manualmente.

Há uma desvantagem nessa configuração padrão. Cada vez que você modificar seu
código C# e o aplicativo ASP.NET Core precisar ser reiniciado, o servidor CRA será
reiniciado também. São necessários alguns segundos para iniciar um backup. Se você
estiver fazendo edições frequentes de código C# e não quiser esperar o servidor CRA
reiniciar, execute o servidor CRA externamente, independentemente do processo do
ASP.NET Core.

Para executar o servidor CRA externamente, alterne para o ClientApp subdiretório em


um prompt de comando e inicie o servidor de desenvolvimento CRA:

Console

cd ClientApp
npm start

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.

Configurar middleware de proxy para SignalR


Para obter mais informações, consulte http-proxy-middleware .

Recursos adicionais
Introdução à autenticação para aplicativos de página única no ASP.NET Core
Usar o modelo de projeto do React com
Redux com o ASP.NET Core
Artigo • 10/01/2023 • 2 minutos para o fim da leitura

) Importante

A partir do ASP.NET Core 6.0, o modelo de projeto React com Redux não está mais
incluído. Para obter mais informações, consulte React Redux: descartando o
suporte no ASP.NET Core 6.0 (aspnet/Announcements #465) .
Usar serviços JavaScript para criar
aplicativos de página única no ASP.NET
Core
Artigo • 04/12/2022 • 13 minutos para o fim da leitura

Por Fiyaz Hasan

2 Aviso

Os recursos descritos neste artigo estão obsoletos a partir do ASP.NET Core 3.0.
Um mecanismo de integração de estruturas SPA mais simples está disponível no
Microsoft. Pacote NuGet AspNetCore.SpaServices.Extensions . Para obter mais
informações, consulte [Comunicado] Obsoleto Microsoft.
AspNetCore.SpaServices e Microsoft. AspNetCore.NodeServices .

Um SPA (Aplicativo de Página Única) é um tipo popular de aplicativo Web devido à sua
experiência de usuário avançada inerente. A integração de estruturas ou bibliotecas SPA
do lado do cliente, como Angular ou React , com estruturas do lado do servidor,
como ASP.NET Core, pode ser difícil. Os Serviços JavaScript foram desenvolvidos para
reduzir o atrito no processo de integração. Ele permite uma operação perfeita entre as
diferentes pilhas de tecnologia de cliente e servidor.

O que são os Serviços JavaScript


Os Serviços JavaScript são uma coleção de tecnologias do lado do cliente para ASP.NET
Core. Sua meta é posicionar ASP.NET Core como a plataforma preferencial do lado do
servidor dos desenvolvedores para a criação de SPAs.

Os Serviços JavaScript consistem em dois pacotes NuGet distintos:

Microsoft. AspNetCore.NodeServices (NodeServices)


Microsoft. AspNetCore.SpaServices (SpaServices)

Esses pacotes são úteis nos seguintes cenários:

Executar JavaScript no servidor


Usar uma estrutura ou biblioteca SPA
Criar ativos do lado do cliente com o Webpack
Grande parte do foco neste artigo é colocado no uso do pacote SpaServices.

O que é SpaServices
SpaServices foi criado para posicionar ASP.NET Core como a plataforma preferencial do
lado do servidor dos desenvolvedores para a criação de SPAs. SpaServices não é
necessário para desenvolver SPAs com ASP.NET Core e não bloqueia os
desenvolvedores em uma estrutura de cliente específica.

SpaServices fornece uma infraestrutura útil, como:

Pré-geração do lado do servidor


Middleware de Desenvolvimento do Webpack
Substituição de módulo frequente
Auxiliares de roteamento

Coletivamente, esses componentes de infraestrutura aprimoram o fluxo de trabalho de


desenvolvimento e a experiência de runtime. Os componentes podem ser adotados
individualmente.

Pré-requisitos para usar SpaServices


Para trabalhar com SpaServices, instale o seguinte:

Node.js (versão 6 ou posterior) com npm

Para verificar se esses componentes estão instalados e podem ser encontrados,


execute o seguinte na linha de comando:

Console

node -v && npm -v

Se estiver implantando em um site do Azure, nenhuma ação será necessária—


Node.js estiver instalado e disponível nos ambientes do servidor.

SDK 2.0 ou posterior do .NET Core


No Windows usando o Visual Studio 2017, o SDK é instalado selecionando a
carga de trabalho de desenvolvimento multiplataforma do .NET Core .

Microsoft. Pacote NuGet AspNetCore.SpaServices


Pré-geração do lado do servidor
Um aplicativo universal (também conhecido como isomórfico) é um aplicativo JavaScript
capaz de ser executado no servidor e no cliente. Angular, React e outras estruturas
populares fornecem uma plataforma universal para esse estilo de desenvolvimento de
aplicativos. A ideia é primeiro renderizar os componentes da estrutura no servidor por
meio de Node.js e, em seguida, delegar uma execução adicional ao cliente.

ASP.NET Core Auxiliares de Marca fornecidos pelo SpaServices simplificam a


implementação da pré-geração do lado do servidor invocando as funções JavaScript no
servidor.

Pré-requisitos de pré-geração do lado do servidor


Instale o pacote npm aspnet-prerendering :

Console

npm i -S aspnet-prerendering

Configuração de pré-geração do lado do servidor


Os Auxiliares de Marca são descobertos por meio do registro de namespace no arquivo
do _ViewImports.cshtml projeto:

CSHTML

@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"

Esses Auxiliares de Marca abstraem os meandros de se comunicar diretamente com APIs


de baixo nível aproveitando uma sintaxe semelhante a HTML dentro da Razor exibição:

CSHTML

<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>

Auxiliar de marcação asp-prerender-module


O asp-prerender-module Auxiliar de Marca, usado no exemplo de código anterior, é
ClientApp/dist/main-server.js executado no servidor por meio de Node.js. Para fins de
clareza, main-server.js o arquivo é um artefato da tarefa de transpilação TypeScript
para JavaScript no processo de build do Webpack . O Webpack define um alias de
ponto de entrada de main-server ; e, a passagem do grafo de dependência para esse
alias começa no ClientApp/boot-server.ts arquivo:

JavaScript

entry: { 'main-server': './ClientApp/boot-server.ts' },

No exemplo Angular a seguir, o ClientApp/boot-server.ts arquivo utiliza a


createServerRenderer função e RenderResult o aspnet-prerendering tipo do pacote

npm para configurar a renderização do servidor por meio de Node.js. A marcação HTML
destinada à renderização do lado do servidor é passada para uma chamada de função
de resolução, que é encapsulada em um objeto JavaScript Promise fortemente tipado. A
Promise significância do objeto é que ele fornece de forma assíncrona a marcação
HTML para a página para injeção no elemento de espaço reservado do DOM.

TypeScript

import { createServerRenderer, RenderResult } from 'aspnet-prerendering';

export default createServerRenderer(params => {


const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url:
params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];

return
platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef
=> {
const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);

return new Promise<RenderResult>((resolve, reject) => {


zone.onError.subscribe(errorInfo => reject(errorInfo));
appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to
delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: state.renderToString()
});
moduleRef.destroy();
});
});
});
});
});

Auxiliar de marca asp-prerender-data


Quando combinado com o asp-prerender-module Auxiliar de Marca, o asp-prerender-
data Auxiliar de Marca pode ser usado para passar informações contextuais da Razor

exibição para o JavaScript do lado do servidor. Por exemplo, a marcação a seguir passa
os dados do usuário para o main-server módulo :

CSHTML

<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
UserName = "John Doe"
}'>Loading...</app>

O argumento recebido UserName é serializado usando o serializador ON interno JSe é


armazenado no params.data objeto . No exemplo de Angular a seguir, os dados são
usados para construir uma saudação personalizada dentro de um h1 elemento :

TypeScript

import { createServerRenderer, RenderResult } from 'aspnet-prerendering';

export default createServerRenderer(params => {


const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url:
params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];

return
platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef
=> {
const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);

return new Promise<RenderResult>((resolve, reject) => {


const result = `<h1>Hello, ${params.data.userName}</h1>`;

zone.onError.subscribe(errorInfo => reject(errorInfo));


appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to
delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: result
});
moduleRef.destroy();
});
});
});
});
});

Os nomes de propriedade passados em Auxiliares de Marca são representados com


notação PascalCase . Contraste isso com JavaScript, em que os mesmos nomes de
propriedade são representados com camelCase. A configuração de serialização ON
padrão JSé responsável por essa diferença.

Para expandir o exemplo de código anterior, os dados podem ser passados do servidor
para a exibição hidratando a globals propriedade fornecida para a resolve função:

TypeScript

import { createServerRenderer, RenderResult } from 'aspnet-prerendering';

export default createServerRenderer(params => {


const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url:
params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];

return
platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef
=> {
const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);

return new Promise<RenderResult>((resolve, reject) => {


const result = `<h1>Hello, ${params.data.userName}</h1>`;

zone.onError.subscribe(errorInfo => reject(errorInfo));


appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to
delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: result,
globals: {
postList: [
'Introduction to ASP.NET Core',
'Making apps with Angular and ASP.NET Core'
]
}
});
moduleRef.destroy();
});
});
});
});
});

A postList matriz definida dentro do globals objeto é anexada ao objeto global


window do navegador. Essa variável de içamento para o escopo global elimina a

duplicação de esforço, especialmente porque se refere ao carregamento dos mesmos


dados uma vez no servidor e novamente no cliente.

Middleware de Desenvolvimento do Webpack


O Middleware de Desenvolvimento do Webpack apresenta um fluxo de trabalho de
desenvolvimento simplificado pelo qual o Webpack cria recursos sob demanda. O
middleware compila e atende automaticamente aos recursos do lado do cliente quando
uma página é recarregada no navegador. A abordagem alternativa é invocar
manualmente o Webpack por meio do script de build npm do projeto quando uma
dependência de terceiros ou o código personalizado é alterado. Um script de build npm
no package.json arquivo é mostrado no exemplo a seguir:

JSON

"build": "npm run build:vendor && npm run build:custom",


Pré-requisitos do Middleware de Desenvolvimento do
Webpack
Instale o pacote npm aspnet-webpack :

Console

npm i -D aspnet-webpack

Configuração do Middleware de Desenvolvimento do


Webpack
O Middleware de Desenvolvimento do Webpack é registrado no pipeline de solicitação
HTTP por meio do seguinte código no Startup.cs método do Configure arquivo:

C#

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

// Call UseWebpackDevMiddleware before UseStaticFiles


app.UseStaticFiles();

O UseWebpackDevMiddleware método de extensão deve ser chamado antes de registrar a


hospedagem de arquivo estático por meio do método de UseStaticFiles extensão. Por
motivos de segurança, registre o middleware somente quando o aplicativo for
executado no modo de desenvolvimento.

A webpack.config.js propriedade do output.publicPath arquivo informa ao middleware


para observar as alterações na dist pasta:

JavaScript

module.exports = (env) => {


output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled,
handles requests for this URL prefix
},
Substituição de módulo frequente
Pense no recurso hmr ( substituição de módulo quente) do Webpack como uma
evolução do Middleware de Desenvolvimento do Webpack. O HMR apresenta todos os
mesmos benefícios, mas simplifica ainda mais o fluxo de trabalho de desenvolvimento
atualizando automaticamente o conteúdo da página depois de compilar as alterações.
Não confunda isso com uma atualização do navegador, o que interferiria no estado
atual na memória e na sessão de depuração do SPA. Há um link dinâmico entre o
serviço Dev Middleware do Webpack e o navegador, o que significa que as alterações
são enviadas por push para o navegador.

Pré-requisitos de substituição de módulo frequente


Instale o pacote npm webpack-hot-middleware :

Console

npm i -D webpack-hot-middleware

Configuração de substituição de módulo frequente


O componente HMR deve ser registrado no pipeline de solicitação HTTP do MVC no
Configure método :

C#

app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});

Como foi verdadeiro com o Middleware de Desenvolvimento do Webpack, o método de


UseWebpackDevMiddleware extensão deve ser chamado antes do método de
UseStaticFiles extensão. Por motivos de segurança, registre o middleware somente

quando o aplicativo for executado no modo de desenvolvimento.

O webpack.config.js arquivo deve definir uma plugins matriz, mesmo que ela seja
deixada vazia:

JavaScript
module.exports = (env) => {
plugins: [new CheckerPlugin()]

Depois de carregar o aplicativo no navegador, a guia Console das ferramentas de


desenvolvedor fornece a confirmação da ativação do HMR:

Auxiliares de roteamento
Na maioria dos SPAs baseados em ASP.NET Core, o roteamento do lado do cliente
geralmente é desejado, além do roteamento do lado do servidor. Os sistemas de
roteamento SPA e MVC podem funcionar de forma independente sem interferência. No
entanto, há um caso de borda que apresenta desafios: identificar 404 respostas HTTP.

Considere o cenário no qual uma rota sem extensão de /some/page é usada. Suponha
que a solicitação não corresponda a 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 nenhuma rota do
lado do servidor ou arquivo estático, é improvável que o aplicativo do lado do cliente o
manipule, geralmente retornando um código de status HTTP 404 desejado.

Pré-requisitos de auxiliares de roteamento


Instale o pacote npm de roteamento do lado do cliente. Usando Angular como
exemplo:
Console

npm i -S @angular/router

Configuração de auxiliares de roteamento


Um método de extensão chamado MapSpaFallbackRoute é usado no Configure método :

C#

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

routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});

As rotas são avaliadas na ordem em que estão configuradas. Consequentemente, a


default rota no exemplo de código anterior é usada primeiro para correspondência de

padrões.

Criar um novo projeto


Os Serviços JavaScript fornecem modelos de aplicativo pré-configurados. SpaServices é
usado nesses modelos em conjunto com diferentes estruturas e bibliotecas, como
Angular, React e Redux.

Esses modelos podem ser instalados por meio da CLI do .NET Core executando o
seguinte comando:

CLI do .NET

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

Uma lista de modelos spa disponíveis é exibida:

Modelos Nome curto Linguagem Marcações

MVC ASP.NET Core com Angular angular [C#] Web/MVC/SPA


Modelos Nome curto Linguagem Marcações

MVC ASP.NET Core com React.js react [C#] Web/MVC/SPA

MVC ASP.NET Core com React.js e Redux reactredux [C#] Web/MVC/SPA

Para criar um novo projeto usando um dos modelos de SPA, inclua o Nome Curto do
modelo no novo comando dotnet . O comando a seguir cria um aplicativo Angular com
ASP.NET Core MVC configurado para o lado do servidor:

CLI do .NET

dotnet new angular

Definir o modo de configuração de runtime


Existem dois modos de configuração de runtime primários:

Desenvolvimento:
Inclui mapas de origem para facilitar a depuração.
Não otimiza o código do lado do cliente para desempenho.
Produção:
Exclui mapas de origem.
Otimiza o código do lado do cliente por meio de agrupamento e minificação.

ASP.NET Core usa uma variável de ambiente chamada ASPNETCORE_ENVIRONMENT para


armazenar o modo de configuração. Para obter mais informações, consulte Definir o
ambiente.

Executar com a CLI do .NET Core


Restaure os pacotes NuGet e npm necessários executando o seguinte comando na raiz
do projeto:

CLI do .NET

dotnet restore && npm i

Crie e execute o aplicativo:

CLI do .NET

dotnet run
O aplicativo começa no localhost de acordo com o modo de configuração de runtime.
Navegar até http://localhost:5000 no navegador exibe a página de aterrissagem.

Executar com o Visual Studio 2017


Abra o .csproj arquivo gerado pelo novo comando dotnet . Os pacotes NuGet e npm
necessários são restaurados automaticamente após a abertura do projeto. Esse processo
de restauração pode levar até alguns minutos e o aplicativo está pronto para ser
executado quando for concluído. Clique no botão de execução verde ou pressione Ctrl
+ F5 e o navegador será aberto na página de aterrissagem do aplicativo. O aplicativo é

executado no localhost de acordo com o modo de configuração de runtime.

Testar o aplicativo
Os modelos spaServices são pré-configurados para executar testes do lado do cliente
usando Karma e Jasmine . Jasmine é uma estrutura de teste de unidade popular
para JavaScript, enquanto Karma é um executor de teste para esses testes. O Karma está
configurado para trabalhar com o Middleware de Desenvolvimento do Webpack de
modo que o desenvolvedor não seja obrigado a parar e executar o teste sempre que
forem feitas alterações. Seja o código em execução no caso de teste ou no caso de teste
em si, o teste será executado automaticamente.

Usando o aplicativo Angular como exemplo, dois casos de teste de Jasmine já são
fornecidos para o CounterComponent no counter.component.spec.ts arquivo:

TypeScript

it('should display a title', async(() => {


const titleText = fixture.nativeElement.querySelector('h1').textContent;
expect(titleText).toEqual('Counter');
}));

it('should start with count 0, then increments by 1 when clicked', async(()


=> {
const countElement = fixture.nativeElement.querySelector('strong');
expect(countElement.textContent).toEqual('0');

const incrementButton = fixture.nativeElement.querySelector('button');


incrementButton.click();
fixture.detectChanges();
expect(countElement.textContent).toEqual('1');
}));

Abra o prompt de comando no diretório ClientApp . Execute o seguinte comando:


Console

npm test

O script inicia o executor de teste karma, que lê as configurações definidas no


karma.conf.js arquivo. Entre outras configurações, o karma.conf.js identifica os

arquivos de teste a serem executados por meio de sua files matriz:

JavaScript

module.exports = function (config) {


config.set({
files: [
'../../wwwroot/dist/vendor.js',
'./boot-tests.ts'
],

Publicar o aplicativo
Consulte este problema do GitHub para obter mais informações sobre como publicar
no Azure.

Combinar os ativos do lado do cliente gerados e os artefatos de ASP.NET Core


publicados em um pacote pronto para implantação pode ser complicado. Felizmente, o
SpaServices orquestra todo esse processo de publicação com um destino personalizado
do MSBuild chamado RunWebpack :

XML

<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">


<!-- As part of publishing, ensure the JS resources are freshly built in
production mode -->
<Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config
webpack.config.vendor.js --env.prod" />
<Exec Command="node node_modules/webpack/bin/webpack.js --env.prod" />

<!-- Include the newly-built files in the publish output -->


<ItemGroup>
<DistFiles Include="wwwroot\dist\**; ClientApp\dist\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')"
Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
O destino do MSBuild tem as seguintes responsabilidades:

1. Restaure os pacotes npm.


2. Crie um build de nível de produção dos ativos do lado do cliente de terceiros.
3. Crie um build de nível de produção dos ativos personalizados do lado do cliente.
4. Copie os ativos gerados pelo Webpack para a pasta de publicação.

O destino do MSBuild é invocado ao executar:

CLI do .NET

dotnet publish -c Release

Recursos adicionais
Angular Docs
Aquisição de biblioteca do lado do
cliente no ASP.NET Core com LibMan
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Por Scott Addie

O Gerenciador de Bibliotecas (LibMan) é uma ferramenta leve de aquisição de


bibliotecas do lado do cliente. O LibMan baixa bibliotecas e estruturas populares do
sistema de arquivos ou de uma CDN (rede de distribuição de conteúdo) . As CDNs
compatíveis incluem CDNJS , jsDelivr e unpkg . Os arquivos de biblioteca
selecionados são buscados e colocados no local adequado dentro do projeto do
ASP.NET Core.

Casos de uso do LibMan


O LibMan oferece os seguintes benefícios:

Somente os arquivos de biblioteca necessários são baixados.


Ferramentas adicionais, tais como Node.js , npm e WebPack , não são
necessárias para adquirir um subconjunto de arquivos em uma biblioteca.
Arquivos podem ser colocados em um local específico sem recorrer a tarefas de
build ou à cópia manual de arquivos.

O LibMan não é um sistema de gerenciamento de pacotes. Se você já está usando um


gerenciador de pacotes, assim como o npm ou yarn , continue fazendo isso. O LibMan
não foi desenvolvido para substituir essas ferramentas.

Recursos adicionais
Usar LibMan com ASP.NET Core no Visual Studio
Usar a CLI do LibMan com o ASP.NET Core
Repositório do GitHub do LibMan
Usar a CLI do LibMan com o ASP.NET
Core
Artigo • 10/01/2023 • 8 minutos para o fim da leitura

Por Scott Addie

A CLI do LibMan é uma ferramenta multiplataforma com suporte em todos os lugares


com suporte do .NET Core.

Pré-requisitos
SDK do .NET Core 2.1 ou posterior

Instalação
Para instalar a CLI do LibMan:

CLI do .NET

dotnet tool install -g Microsoft.Web.LibraryManager.Cli

Uma Ferramenta Global do .NET Core é instalada no pacote NuGet


Microsoft.Web.LibraryManager.Cli .

Para instalar a CLI do LibMan de uma fonte de pacote NuGet específica:

CLI do .NET

dotnet tool install -g Microsoft.Web.LibraryManager.Cli --version 1.0.94-


g606058a278 --add-source C:\Temp\

No exemplo anterior, uma Ferramenta Global do .NET Core é instalada do arquivo


C:\Temp\Microsoft.Web.LibraryManager.Cli.1.0.94-g606058a278.nupkg do computador
local.

Uso
Após a instalação bem-sucedida da CLI, o seguinte comando pode ser usado:

Console
libman

Para exibir a versão da CLI instalada:

Console

libman --version

Para exibir os comandos disponíveis da CLI:

Console

libman --help

O comando anterior exibe uma saída semelhante à seguinte:

Console

1.0.163+g45474d37ed

Usage: libman [options] [command]

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

Use "libman [command] --help" for more information about a command.

As seções a seguir descrevem os comandos da CLI disponíveis.

Inicializar o LibMan no projeto


O libman init comando criará um libman.json arquivo se um não existir. O arquivo é
criado com o conteúdo do modelo de item padrão.

Sinopse
Console

libman init [-d|--default-destination] [-p|--default-provider] [--verbosity]


libman init [-h|--help]

Opções
As opções a seguir estão disponíveis para o comando libman init :

-d|--default-destination <PATH>

Um caminho relativo à pasta atual. Os arquivos de biblioteca serão instalados


nesse local se nenhuma destination propriedade for definida para uma biblioteca
no libman.json . O <PATH> valor é gravado na defaultDestination propriedade de
libman.json .

-p|--default-provider <PROVIDER>

O provedor a ser usado se nenhum provedor for definido para uma determinada
biblioteca. O <PROVIDER> valor é gravado na defaultProvider propriedade de
libman.json . Substitua por <PROVIDER> um dos seguintes valores:

cdnjs
filesystem

jsdelivr

unpkg

-h|--help

Mostra informações da Ajuda.

--verbosity <LEVEL>

Defina a verbosidade da saída. Substitua por <LEVEL> um dos seguintes valores:


quiet
normal

detailed
Exemplos
Para criar um libman.json arquivo em um projeto ASP.NET Core:

Navegue até a raiz do projeto.

Execute o seguinte comando:

Console

libman init

Digite o nome do provedor padrão ou pressione Enter para usar o provedor de


CDNJS padrão. Os valores válidos incluem:
cdnjs
filesystem

jsdelivr

unpkg

Um libman.json arquivo é adicionado à raiz do projeto com o seguinte conteúdo:

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}

Adicionar arquivos de biblioteca


O libman install comando baixa e instala arquivos de biblioteca no projeto. Um
libman.json arquivo será adicionado se um não existir. O libman.json arquivo é
modificado para armazenar os detalhes de configuração dos arquivos de biblioteca.

Sinopse
Console

libman install <LIBRARY> [-d|--destination] [--files] [-p|--provider] [--


verbosity]
libman install [-h|--help]

Argumentos
LIBRARY

O nome da biblioteca a ser instalada. Esse nome pode incluir notação de número de
versão (por exemplo, @1.2.0 ).

Opções
As opções a seguir estão disponíveis para o comando libman install :

-d|--destination <PATH>

O local para instalar a biblioteca. Se não for especificado, o local padrão será
usado. Se nenhuma defaultDestination propriedade for especificada no
libman.json , essa opção será necessária.

--files <FILE>

Especifique o nome do arquivo a ser instalado da biblioteca. Se não for


especificado, todos os arquivos da biblioteca serão instalados. Forneça uma --
files opção por arquivo a ser instalado. Também há suporte para caminhos
relativos. Por exemplo: --files dist/browser/signalr.js .

-p|--provider <PROVIDER>

O nome do provedor a ser usado para a aquisição da biblioteca. Substitua por


<PROVIDER> um dos seguintes valores:

cdnjs
filesystem
jsdelivr

unpkg

Se não for especificado, a defaultProvider propriedade em libman.json será


usada. Se nenhuma defaultProvider propriedade for especificada no libman.json ,
essa opção será necessária.

-h|--help

Mostra informações da Ajuda.

--verbosity <LEVEL>

Defina a verbosidade da saída. Substitua por <LEVEL> um dos seguintes valores:


quiet

normal

detailed

Exemplos
Considere o seguinte arquivo libman.json :

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}

Para instalar o arquivo jQuery versão 3.2.1 jquery.min.js na pasta


wwwroot/scripts/jquery usando o provedor CDNJS :

Console

libman install jquery@3.2.1 --provider cdnjs --destination


wwwroot/scripts/jquery --files jquery.min.js

O libman.json arquivo é semelhante ao seguinte:

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.2.1",
"destination": "wwwroot/scripts/jquery",
"files": [
"jquery.min.js"
]
}
]
}

Para instalar os calendar.js arquivos e calendar.css de C:\temp\contosoCalendar\


usando o provedor do sistema de arquivos:

Console

libman install C:\temp\contosoCalendar\ --provider filesystem --files


calendar.js --files calendar.css

O seguinte prompt é exibido por dois motivos:

O libman.json arquivo não contém uma defaultDestination propriedade .


O libman install comando não contém a opção -d|--destination .

Depois de aceitar o destino padrão, o libman.json arquivo se assemelha ao seguinte:

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.2.1",
"destination": "wwwroot/scripts/jquery",
"files": [
"jquery.min.js"
]
},
{
"library": "C:\\temp\\contosoCalendar\\",
"provider": "filesystem",
"destination": "wwwroot/lib/contosoCalendar",
"files": [
"calendar.js",
"calendar.css"
]
}
]
}

Restaurar arquivos de biblioteca


O libman restore comando instala arquivos de biblioteca definidos em libman.json . As
seguintes regras se aplicam:

Se nenhum libman.json arquivo existir na raiz do projeto, um erro será retornado.


Se uma biblioteca especificar um provedor, a defaultProvider propriedade em
libman.json será ignorada.
Se uma biblioteca especificar um destino, a defaultDestination propriedade em
libman.json será ignorada.

Sinopse
Console

libman restore [--verbosity]


libman restore [-h|--help]

Opções
As opções a seguir estão disponíveis para o comando libman restore :

-h|--help

Mostra informações da Ajuda.

--verbosity <LEVEL>
Defina a verbosidade da saída. Substitua por <LEVEL> um dos seguintes valores:
quiet
normal

detailed

Exemplos
Para restaurar os arquivos de biblioteca definidos em libman.json :

Console

libman restore

Excluir arquivos de biblioteca


O libman clean comando exclui arquivos de biblioteca restaurados anteriormente por
meio do LibMan. Pastas que ficam vazias depois que essa operação é excluída. As
configurações associadas dos arquivos de biblioteca na libraries propriedade de
libman.json não são removidas.

Sinopse
Console

libman clean [--verbosity]


libman clean [-h|--help]

Opções
As opções a seguir estão disponíveis para o comando libman clean :

-h|--help

Mostra informações da Ajuda.

--verbosity <LEVEL>

Defina a verbosidade da saída. Substitua por <LEVEL> um dos seguintes valores:


quiet

normal
detailed

Exemplos
Para excluir arquivos de biblioteca instalados por meio do LibMan:

Console

libman clean

Desinstalar arquivos de biblioteca


O comando libman uninstall :

Exclui todos os arquivos associados à biblioteca especificada do destino em


libman.json .
Remove a configuração da biblioteca associada de libman.json .

Ocorre um erro quando:

Não libman.json existe nenhum arquivo na raiz do projeto.


A biblioteca especificada não existe.

Se mais de uma biblioteca com o mesmo nome estiver instalada, você será solicitado a
escolher uma.

Sinopse
Console

libman uninstall <LIBRARY> [--verbosity]


libman uninstall [-h|--help]

Argumentos
LIBRARY

O nome da biblioteca a ser desinstalada. Esse nome pode incluir notação de número de
versão (por exemplo, @1.2.0 ).

Opções
As opções a seguir estão disponíveis para o comando libman uninstall :

-h|--help

Mostra informações da Ajuda.

--verbosity <LEVEL>

Defina o detalhamento da saída. Substitua por <LEVEL> um dos seguintes valores:


quiet

normal
detailed

Exemplos
Considere o seguinte arquivo libman.json :

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.3.1",
"files": [
"jquery.min.js",
"jquery.js",
"jquery.min.map"
],
"destination": "wwwroot/lib/jquery/"
},
{
"provider": "unpkg",
"library": "bootstrap@4.1.3",
"destination": "wwwroot/lib/bootstrap/"
},
{
"provider": "filesystem",
"library": "C:\\temp\\lodash\\",
"files": [
"lodash.js",
"lodash.min.js"
],
"destination": "wwwroot/lib/lodash/"
}
]
}
Para desinstalar o jQuery, um dos seguintes comandos é bem-sucedido:

Console

libman uninstall jquery

Console

libman uninstall jquery@3.3.1

Para desinstalar os arquivos Lodash instalados por meio do filesystem provedor:

Console

libman uninstall C:\temp\lodash\

Atualizar versão da biblioteca


O libman update comando atualiza uma biblioteca instalada por meio do LibMan para a
versão especificada.

Ocorre um erro quando:

Não libman.json existe nenhum arquivo na raiz do projeto.


A biblioteca especificada não existe.

Se mais de uma biblioteca com o mesmo nome estiver instalada, você será solicitado a
escolher uma.

Sinopse
Console

libman update <LIBRARY> [-pre] [--to] [--verbosity]


libman update [-h|--help]

Argumentos
LIBRARY

O nome da biblioteca a ser atualizada.


Opções
As opções a seguir estão disponíveis para o comando libman update :

-pre

Obtenha a versão de pré-lançamento mais recente da biblioteca.

--to <VERSION>

Obtenha uma versão específica da biblioteca.

-h|--help

Mostra informações da Ajuda.

--verbosity <LEVEL>

Defina o detalhamento da saída. Substitua por <LEVEL> um dos seguintes valores:


quiet

normal
detailed

Exemplos
Para atualizar o jQuery para a versão mais recente:

Console

libman update jquery

Para atualizar o jQuery para a versão 3.3.1:

Console

libman update jquery --to 3.3.1

Para atualizar o jQuery para a versão de pré-lançamento mais recente:

Console

libman update jquery -pre


Gerenciar cache de biblioteca
O libman cache comando gerencia o cache da biblioteca LibMan. O filesystem
provedor não usa o cache da biblioteca.

Sinopse
Console

libman cache clean [<PROVIDER>] [--verbosity]


libman cache list [--files] [--libraries] [--verbosity]
libman cache [-h|--help]

Argumentos
PROVIDER

Usado somente com o clean comando . Especifica o cache do provedor a ser limpo. Os
valores válidos incluem:

cdnjs

filesystem
jsdelivr

unpkg

Opções
As opções a seguir estão disponíveis para o comando libman cache :

--files

Liste os nomes dos arquivos armazenados em cache.

--libraries

Liste os nomes das bibliotecas armazenadas em cache.

-h|--help

Mostra informações da Ajuda.

--verbosity <LEVEL>
Defina o detalhamento da saída. Substitua por <LEVEL> um dos seguintes valores:
quiet
normal

detailed

Exemplos
Para exibir os nomes das bibliotecas armazenadas em cache por provedor, use um
dos seguintes comandos:

Console

libman cache list

Console

libman cache list --libraries

Uma saída semelhante à apresentada a seguir será exibida:

Console

Cache contents:
---------------
unpkg:
knockout
react
vue
cdnjs:
font-awesome
jquery
knockout
lodash.js
react

Para exibir os nomes dos arquivos de biblioteca armazenados em cache por


provedor:

Console

libman cache list --files

Uma saída semelhante à apresentada a seguir será exibida:

Console
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 as versões 3.2.1 e 3.3.1 do jQuery são
armazenadas em cache no provedor cdnJS .

Para esvaziar o cache da biblioteca para o provedor de CDNJS :

Console

libman cache clean cdnjs

Depois de esvaziar o cache do provedor cdnJS , o libman cache list comando


exibe o seguinte:
Console

Cache contents:
---------------
unpkg:
knockout
react
vue
cdnjs:
(empty)

Para esvaziar o cache para todos os provedores com suporte:

Console

libman cache clean

Depois de esvaziar todos os caches de provedor, o libman cache list comando


exibe o seguinte:

Console

Cache contents:
---------------
unpkg:
(empty)
cdnjs:
(empty)

Recursos adicionais
Instalar uma ferramenta global
Usar LibMan com ASP.NET Core no Visual Studio
Repositório do GitHub do LibMan
Usar LibMan com ASP.NET Core no
Visual Studio
Artigo • 28/11/2022 • 8 minutos para o fim da leitura

Por Scott Addie

O Visual Studio tem suporte interno para o LibMan em projetos ASP.NET Core,
incluindo:

Suporte para configurar e executar operações de restauração do LibMan no build.


Itens de menu para disparar operações de restauração e limpeza do LibMan.
Pesquise a caixa de diálogo para localizar bibliotecas e adicionar os arquivos a um
projeto.
Edição de suporte para libman.json — o arquivo de manifesto LibMan.

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

Pré-requisitos
Visual Studio 2019 com a carga de trabalho de ASP.NET e desenvolvimento web

Adicionar arquivos de biblioteca


Arquivos de biblioteca podem ser adicionados a um projeto ASP.NET Core de duas
maneiras diferentes:

1. Usar a caixa de diálogo Adicionar biblioteca Client-Side


2. Configurar manualmente entradas de arquivo de manifesto do LibMan

Usar a caixa de diálogo Adicionar biblioteca Client-Side


Siga estas etapas para instalar uma biblioteca do lado do cliente:

Em Gerenciador de Soluções, clique com o botão direito do mouse na pasta do


projeto na qual os arquivos devem ser adicionados. Escolha Adicionar>Biblioteca
do Lado do Cliente. A caixa de diálogo Adicionar biblioteca Client-Side é exibida:
Selecione o provedor de biblioteca na lista suspensa Provedor . A CDNJS é o
provedor padrão.

Digite o nome da biblioteca a ser buscado na caixa de texto Biblioteca . O


IntelliSense fornece uma lista de bibliotecas que começam com o texto fornecido.

Selecione a biblioteca na lista do IntelliSense. Observe que o nome da biblioteca é


sufixo com o @ símbolo e a versão estável mais recente conhecida pelo provedor
selecionado.

Decida quais arquivos incluir:


Selecione o botão Incluir todos os arquivos de biblioteca para incluir todos os
arquivos da biblioteca.
Selecione o botão Escolher arquivos específicos para incluir um subconjunto
dos arquivos da biblioteca. Quando o botão de opção é selecionado, a árvore
do seletor de arquivos é habilitada. Marque as caixas à esquerda dos nomes de
arquivo a serem baixados.

Especifique a pasta do projeto para armazenar os arquivos na caixa de texto Local


de Destino . Como recomendação, armazene cada biblioteca em uma pasta
separada.

A pasta Local de Destino sugerida baseia-se no local do qual a caixa de diálogo foi
iniciada:
Se iniciado a partir da raiz do projeto:
wwwroot/lib será usado se wwwroot existir.
lib é usado se wwwroot não existe.
Se iniciado a partir de uma pasta de projeto, o nome da pasta correspondente
será usado.

A sugestão de pasta é sufixo com o nome da biblioteca. A tabela a seguir ilustra


sugestões de pasta ao instalar o jQuery em um Razor projeto de Páginas.

Local de inicialização Pasta sugerida

raiz do projeto (se wwwroot existir) wwwroot/lib/jquery/

raiz do projeto (se wwwroot não existir) lib/jquery/

Pasta Páginas no projeto Pages/jquery/

Clique no botão Instalar para baixar os arquivos, de acordo com a configuração


em libman.json .

Examine o feed do Gerenciador de Bibliotecas da janela Saída para obter detalhes


da instalação. Por exemplo:

Console

Restore operation started...


Restoring libraries for project LibManSample
Restoring library jquery@3.3.1... (LibManSample)
wwwroot/lib/jquery/jquery.min.js written to destination (LibManSample)
wwwroot/lib/jquery/jquery.js written to destination (LibManSample)
wwwroot/lib/jquery/jquery.min.map written to destination (LibManSample)
Restore operation completed
1 libraries restored in 2.32 seconds

Configurar manualmente entradas de arquivo de


manifesto do LibMan
Todas as operações do LibMan no Visual Studio são baseadas no conteúdo do
manifesto LibMan da raiz do projeto ( libman.json ). Você pode editar libman.json
manualmente para configurar arquivos de biblioteca para o projeto. O Visual Studio
restaura todos os arquivos de biblioteca uma vez libman.json salvos.

Para abrir libman.json para edição, existem as seguintes opções:

Clique duas vezes no libman.json arquivo no Gerenciador de Soluções.


Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e
selecione Gerenciar Bibliotecas Client-Side. †
Selecione Gerenciar bibliotecas Client-Side no menu projeto do Visual Studio. †
† Se o libman.json arquivo ainda não existir na raiz do projeto, ele será criado com o
conteúdo do modelo de item padrão.

O Visual Studio oferece JSsuporte avançado à edição ON, como colorização,


formatação, IntelliSense e validação de esquema. O esquema ON do JSmanifesto
LibMan é encontrado em https://json.schemastore.org/libman .

Com o arquivo de manifesto a seguir, o LibMan recupera arquivos de acordo com a


configuração definida na libraries propriedade. Uma explicação dos literais de objeto
definidos a libraries seguir:

Um subconjunto do jQuery versão 3.3.1 é recuperado do provedor de CDNJS . O


subconjunto é definido na files propriedade — jquery.js jquery.min.js e
jquery.min.map. Os arquivos são colocados na pasta wwwroot/lib/jquery do
projeto.
A totalidade do Bootstrap versão 4.1.3 é recuperada e colocada em uma pasta
wwwroot/lib/bootstrap . A propriedade do literal do objeto substitui o valor da
provider defaultProvider propriedade. O LibMan recupera os arquivos Bootstrap

do provedor unpkg.
Um subconjunto de Lodash foi aprovado por um órgão regulador dentro da
organização. Os lodash.js arquivos e os arquivos lodash.min.js são recuperados
do sistema de arquivos local em C:\temp\lodash\. Os arquivos são copiados para a
pasta wwwroot/lib/lodash do projeto.

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.3.1",
"files": [
"jquery.min.js",
"jquery.js",
"jquery.min.map"
],
"destination": "wwwroot/lib/jquery/"
},
{
"provider": "unpkg",
"library": "bootstrap@4.1.3",
"destination": "wwwroot/lib/bootstrap/"
},
{
"provider": "filesystem",
"library": "C:\\temp\\lodash\\",
"files": [
"lodash.js",
"lodash.min.js"
],
"destination": "wwwroot/lib/lodash/"
}
]
}

7 Observação

O LibMan dá suporte apenas a uma versão de cada biblioteca de cada provedor. O


libman.json arquivo falhará na validação do esquema se ele contiver duas

bibliotecas com o mesmo nome de biblioteca para um determinado provedor.

Restaurar arquivos de biblioteca


Para restaurar arquivos de biblioteca de dentro do Visual Studio, deve haver um arquivo
válido libman.json na raiz do projeto. Os arquivos restaurados são colocados no
projeto no local especificado para cada biblioteca.

Os arquivos de biblioteca podem ser restaurados em um projeto de ASP.NET Core de


duas maneiras:

1. Restaurar arquivos durante o build


2. Restaurar arquivos manualmente

Restaurar arquivos durante o build


O LibMan pode restaurar os arquivos de biblioteca definidos como parte do processo
de build. Por padrão, o comportamento de restauração no build está desabilitado.

Para habilitar e testar o comportamento de restauração no build:

Clique com o botão libman.json direito do mouse em Gerenciador de Soluções e


selecione Habilitar Restaurar bibliotecas Client-Side no Build no menu de
contexto.

Clique no botão Sim quando solicitado a instalar um pacote NuGet. O pacote


NuGet Microsoft.Web.LibraryManager.Build é adicionado ao projeto:

XML
<PackageReference Include="Microsoft.Web.LibraryManager.Build"
Version="1.0.113" />

Crie o projeto para confirmar a restauração do arquivo LibMan. O


Microsoft.Web.LibraryManager.Build pacote injeta um destino MSBuild que
executa o LibMan durante a operação de build do projeto.

Examine o feed de build da janela Saída para um log de atividades do LibMan:

Console

1>------ Build started: Project: LibManSample, Configuration: Debug Any


CPU ------
1>
1>Restore operation started...
1>Restoring library jquery@3.3.1...
1>Restoring library bootstrap@4.1.3...
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 menu de contexto


exibe uma opção libman.json Desabilitar Bibliotecas Client-Side de Restauração na
compilação. Selecionar essa opção remove a Microsoft.Web.LibraryManager.Build
referência do pacote do arquivo de projeto. Consequentemente, as bibliotecas do lado
do cliente não são mais restauradas em cada build.

Independentemente da configuração de restauração no build, você pode restaurar


manualmente a qualquer momento no libman.json menu de contexto. Para obter mais
informações, consulte Restaurar arquivos manualmente.

Restaurar arquivos manualmente


Para restaurar manualmente arquivos de biblioteca:

Para todos os projetos na solução:


Clique com o botão direito do mouse no nome da solução no Gerenciador de
Soluções.
Selecione a opção Restaurar bibliotecas Client-Side .
Para um projeto específico:
Clique com o botão direito do mouse no libman.json arquivo em Gerenciador
de Soluções.
Selecione a opção Restaurar bibliotecas Client-Side .

Enquanto a operação de restauração está em execução:

O ícone do TSC (Task Status Center) na barra de status do Visual Studio será
animado e lerá a operação de restauração iniciada. Clicar no ícone abre uma dica
de ferramenta listando as tarefas conhecidas em segundo plano.

As mensagens serão enviadas para a barra de status e o feed do Gerenciador de


Bibliotecas da janela Saída . Por exemplo:

Console

Restore operation started...


Restoring libraries for project LibManSample
Restoring library jquery@3.3.1... (LibManSample)
wwwroot/lib/jquery/jquery.min.js written to destination (LibManSample)
wwwroot/lib/jquery/jquery.js written to destination (LibManSample)
wwwroot/lib/jquery/jquery.min.map written to destination (LibManSample)
Restore operation completed
1 libraries restored in 2.32 seconds

Excluir arquivos de biblioteca


Para executar a operação limpa , que exclui arquivos de biblioteca restaurados
anteriormente no Visual Studio:

Clique com o botão direito do mouse no libman.json arquivo em Gerenciador de


Soluções.
Selecione a opção Limpar bibliotecas de Client-Side .

Para evitar a remoção não intencional de arquivos que não são de biblioteca, a
operação limpa não exclui diretórios inteiros. Ele só remove arquivos que foram
incluídos na restauração anterior.

Enquanto a operação limpa está em execução:

O ícone TSC na barra de status do Visual Studio será animado e lerá a operação de
bibliotecas de clientes iniciada. Clicar no ícone abre uma dica de ferramenta
listando as tarefas conhecidas em segundo plano.
As mensagens são enviadas para a barra de status e o feed do Gerenciador de
Bibliotecas da janela Saída . Por exemplo:
Console

Clean libraries operation started...


Clean libraries operation completed
2 libraries were successfully deleted in 1.91 secs

A operação limpa exclui apenas arquivos do projeto. Os arquivos de biblioteca


permanecem no cache para recuperação mais rápida em operações futuras de
restauração. Para gerenciar arquivos de biblioteca armazenados no cache do
computador local, use a CLI do LibMan.

Desinstalar arquivos de biblioteca


Para desinstalar arquivos de biblioteca:

Abra o libman.json .

Posicione o caret dentro do literal do objeto correspondente libraries .

Clique no ícone de lâmpada que aparece na margem esquerda e selecione


Desinstalar <>library_name@<library_version>:

Como alternativa, você pode editar e salvar manualmente o manifesto do LibMan


( libman.json ). A operação de restauração é executada quando o arquivo é salvo. Os
arquivos de biblioteca que não estão mais definidos são libman.json removidos do
projeto.

Atualizar versão da biblioteca


Para verificar se há uma versão atualizada da biblioteca:

Abra o libman.json .
Posicione o caret dentro do literal do objeto correspondente libraries .
Clique no ícone de lâmpada que aparece na margem esquerda. Passe o mouse
sobre Verificar se há atualizações.

O LibMan verifica se há uma versão da biblioteca mais recente do que a versão


instalada. Os seguintes resultados podem ocorrer:

Uma mensagem Sem atualizações encontrada será exibida se a versão mais


recente já estiver instalada.

A versão estável mais recente será exibida se ainda não estiver instalada.

Se uma pré-versão mais recente que a versão instalada estiver disponível, a pré-
versão será exibida.

Para fazer downgrade para uma versão mais antiga da biblioteca, edite manualmente o
libman.json arquivo. Quando o arquivo é salvo, a operação de restauração do LibMan:

Remove arquivos redundantes da versão anterior.


Adiciona arquivos novos e atualizados da nova versão.

Recursos adicionais
Usar a CLI do LibMan com o ASP.NET Core
Repositório do GitHub do LibMan
Usar Grunt em ASP.NET Core
Artigo • 10/01/2023 • 9 minutos para o fim da leitura

O Grunt é um executor de tarefas JavaScript que automatiza a minificação de script, a


compilação do TypeScript, as ferramentas "lint" de qualidade de código, os pré-
processadores CSS e praticamente qualquer tarefa repetitiva que precise ser feita para
dar suporte ao desenvolvimento do cliente. O Grunt tem suporte total no Visual Studio.

Este exemplo usa um projeto de ASP.NET Core vazio como ponto de partida para
mostrar como automatizar o processo de build do cliente do zero.

O exemplo concluído limpa o diretório de implantação de destino, combina arquivos


JavaScript, verifica a qualidade do código, condensa o conteúdo do arquivo JavaScript e
implanta na raiz do aplicativo Web. Usaremos os seguintes pacotes:

grunt: o pacote do executor de tarefas grunt.

grunt-contrib-clean: um plug-in que remove arquivos ou diretórios.

grunt-contrib-jshint: um plug-in que analisa a qualidade do código JavaScript.

grunt-contrib-concat: um plug-in que une arquivos em um único arquivo.

grunt-contrib-uglify: um plug-in que minifia JavaScript para reduzir o tamanho.

grunt-contrib-watch: um plug-in que observa a atividade do arquivo.

Preparando o aplicativo
Para começar, configure um novo aplicativo Web vazio e adicione arquivos de exemplo
typeScript. Os arquivos TypeScript são compilados automaticamente em JavaScript
usando as configurações padrão do Visual Studio e serão nossa matéria-prima para
processar usando Grunt.

1. No Visual Studio, crie um novo ASP.NET Web Application .

2. Na caixa de diálogo Novo projeto ASP.NET, selecione o modelo ASP.NET Core


Vazio e clique no botão OK.

3. No Gerenciador de Soluções, examine a estrutura do projeto. A \src pasta inclui


nós e Dependencies vazios wwwroot .
4. Adicione uma nova pasta chamada TypeScript ao diretório do projeto.

5. Antes de adicionar arquivos, verifique se o Visual Studio tem a opção "compilar ao


salvar" para arquivos TypeScript marcada. Navegue até Ferramentas>Opções>
Projetotypescript>do Editor> de Texto:

6. Clique com o botão direito do mouse no TypeScript diretório e selecione


Adicionar > Novo Item no menu de contexto. Selecione o item de arquivo
JavaScript e nomeie o arquivo Tastes.ts (observe a extensão *.ts). Copie a linha
do código TypeScript abaixo para o arquivo (quando você salvar, um novo
Tastes.js arquivo aparecerá com a origem JavaScript).

TypeScript

enum Tastes { Sweet, Sour, Salty, Bitter }

7. Adicione um segundo arquivo ao diretório TypeScript e nomeie-o Food.ts como .


Copie o código abaixo para o arquivo.

TypeScript
class Food {
constructor(name: string, calories: number) {
this._name = name;
this._calories = calories;
}

private _name: string;


get Name() {
return this._name;
}

private _calories: number;


get Calories() {
return this._calories;
}

private _taste: Tastes;


get Taste(): Tastes { return this._taste }
set Taste(value: Tastes) {
this._taste = value;
}
}

Configurando o NPM
Em seguida, configure o NPM para baixar grunt e grunt-tasks.

1. No Gerenciador de Soluções, clique com o botão direito do mouse no projeto e


selecione Adicionar > Novo Item no menu de contexto. Selecione o item de
arquivo de configuração NPM , deixe o nome padrão, package.json e clique no
botão Adicionar .

2. package.json No arquivo, dentro das chaves de devDependencies objeto, insira


"grunt". Selecione grunt na lista Intellisense e pressione a tecla Enter. O Visual
Studio citará o nome do pacote grunhido e adicionará dois-pontos. À direita dos
dois-pontos, selecione a versão estável mais recente do pacote na parte superior
da lista do Intellisense (pressione Ctrl-Space se o Intellisense não aparecer).

7 Observação
O NPM usa o controle de versão semântico para organizar dependências.
O controle de versão semântico, também conhecido como SemVer, identifica
pacotes com o esquema <de numeração principal>.< menor>.< patch>. 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 cursor (^) corresponde à versão principal mais recente e o bloco
(~) corresponde à versão secundária mais recente. Consulte a referência do
analisador de versão semver do NPM como um guia para a expressividade
completa que o SemVer fornece.

3. Adicione mais dependências para carregar pacotes grunt-contrib-* para limpar,


jshint, concat, uglify e assistir , conforme mostrado no exemplo abaixo. As versões
não precisam corresponder ao exemplo.

JSON

"devDependencies": {
"grunt": "0.4.5",
"grunt-contrib-clean": "0.6.0",
"grunt-contrib-jshint": "0.11.0",
"grunt-contrib-concat": "0.5.1",
"grunt-contrib-uglify": "0.8.0",
"grunt-contrib-watch": "0.6.1"
}

4. Salve o arquivo package.json .

Os pacotes para cada devDependencies item serão baixados, juntamente com todos os
arquivos necessários para cada pacote. Você pode encontrar os arquivos de pacote no
diretório node_modules habilitando o botão Mostrar Todos os Arquivos no Gerenciador
de Soluções.

7 Observação

Se precisar, você poderá restaurar manualmente as dependências no Gerenciador


de Soluções clicando com o botão direito do mouse e Dependencies\NPM
selecionando a opção de menu Restaurar Pacotes.

Configurando o Grunt
O Grunt é configurado usando um manifesto chamado Gruntfile.js que define,
carrega e registra tarefas que podem ser executadas manualmente ou configuradas
para serem executadas automaticamente com base em eventos no Visual Studio.

1. Clique com o botão direito do mouse no projeto e selecione Adicionar>Novo


Item. Selecione o modelo de item Arquivo JavaScript , altere o nome para
Gruntfile.js e clique no botão Adicionar .

2. Adicione o código a seguir a Gruntfile.js . A initConfig função define opções


para cada pacote e o restante do módulo carrega e registra tarefas.

JavaScript

module.exports = function (grunt) {


grunt.initConfig({
});
};

3. Dentro da initConfig função , adicione opções para a clean tarefa, conforme


mostrado no exemplo Gruntfile.js abaixo. A clean tarefa aceita uma matriz de
cadeias de caracteres de diretório. Essa tarefa remove arquivos de wwwroot/lib e
remove todo o diretório /temp .

JavaScript

module.exports = function (grunt) {


grunt.initConfig({
clean: ["wwwroot/lib/*", "temp/"],
});
};

4. Abaixo da initConfig função , adicione uma chamada a grunt.loadNpmTasks . Isso


tornará a tarefa executável no Visual Studio.

JavaScript

grunt.loadNpmTasks("grunt-contrib-clean");

5. Salvar Gruntfile.js . O arquivo deve ser semelhante à captura de tela abaixo.

6. Clique com o botão Gruntfile.js direito do mouse e selecione Gerenciador de


Executores de Tarefas no menu de contexto. A janela Gerenciador do Executor de
Tarefas será aberta.

7. Verifique se isso clean é mostrado em Tarefas no Gerenciador de Executores de


Tarefas.

8. Clique com o botão direito do mouse na tarefa limpa e selecione Executar no


menu de contexto. Uma janela de comando exibe o progresso da tarefa.
7 Observação

Ainda não há arquivos ou diretórios a serem limpos. Se desejar, você pode


criá-los manualmente no Gerenciador de Soluções e, em seguida, executar a
tarefa limpa como um teste.

9. initConfig Na função , adicione uma entrada para concat usar o código abaixo.

A src matriz de propriedades lista os arquivos a serem combinados, na ordem em


que eles devem ser combinados. A dest propriedade atribui o caminho ao arquivo
combinado produzido.

JavaScript

concat: {
all: {
src: ['TypeScript/Tastes.js', 'TypeScript/Food.js'],
dest: 'temp/combined.js'
}
},

7 Observação

A all propriedade no código acima é o nome de um destino. Os destinos


são usados em algumas tarefas Grunt para permitir vários ambientes de build.
Você pode exibir os destinos internos usando o IntelliSense ou atribuir seus
próprios.

10. Adicione a jshint tarefa usando o código abaixo.

O utilitário jshint code-quality é executado em todos os arquivos JavaScript


encontrados no diretório temp .
JavaScript

jshint: {
files: ['temp/*.js'],
options: {
'-W069': false,
}
},

7 Observação

A opção "-W069" é um erro produzido por jshint quando JavaScript usa


sintaxe de colchete para atribuir uma propriedade em vez de 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 continue.

11. Adicione a uglify tarefa usando o código abaixo.

A tarefa minimiza o combined.js arquivo encontrado no diretório temp e cria o


arquivo de resultado em wwwroot/lib seguindo o nome> do arquivo de
convenção<de nomenclatura padrão.min.js.

JavaScript

uglify: {
all: {
src: ['temp/combined.js'],
dest: 'wwwroot/lib/combined.min.js'
}
},

12. Na chamada para grunt.loadNpmTasks que carrega grunt-contrib-clean , inclua a


mesma chamada para jshint, concat e uglify usando o código abaixo.

JavaScript

grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');

13. Salvar Gruntfile.js . O arquivo deve ser semelhante ao exemplo abaixo.


14. Observe que a lista Tarefas do Gerenciador de Tarefas do Executor de Tarefas inclui
clean tarefas , concat e jshint uglify . Execute cada tarefa em ordem e observe os

resultados em Gerenciador de Soluções. Cada tarefa deve ser executada sem


erros.

A tarefa concat cria um novo combined.js arquivo e o coloca no diretório


temporário. A jshint tarefa simplesmente é executada e não produz saída. A
uglify tarefa cria um novo combined.min.js arquivo e o coloca em wwwroot/lib.

Após a conclusão, a solução deve ser semelhante à captura de tela abaixo:


7 Observação

Para obter mais informações sobre as opções de cada pacote, visite


https://www.npmjs.com/ e pesquise o nome do pacote na caixa de
pesquisa na página principal. Por exemplo, você pode pesquisar o pacote
grunt-contrib-clean para obter um link de documentação que explica todos
os seus parâmetros.

Todos juntos agora


Use o método Grunt registerTask() para executar uma série de tarefas em uma
sequência específica. Por exemplo, para executar as etapas de exemplo acima na ordem
clean -> concat -> jshint -> uglify, adicione o código abaixo ao módulo. O código deve
ser adicionado ao mesmo nível que as chamadas loadNpmTasks(), fora initConfig.

JavaScript

grunt.registerTask("all", ['clean', 'concat', 'jshint', 'uglify']);

A nova tarefa aparece no Gerenciador de Executores de Tarefas em Tarefas de Alias.


Você pode clicar com o botão direito do mouse e executá-lo da mesma forma que faria
com outras tarefas. A all tarefa executará clean , concat jshint e uglify , na ordem.
Monitorando alterações
Uma 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 a initConfig para
observar as alterações em *.js arquivos no diretório TypeScript. Se um arquivo JavaScript
for alterado, watch executará a all tarefa.

JavaScript

watch: {
files: ["TypeScript/*.js"],
tasks: ["all"]
}

Adicione uma chamada para loadNpmTasks() para mostrar a watch tarefa no


Gerenciador de Executores de Tarefas.

JavaScript

grunt.loadNpmTasks('grunt-contrib-watch');

Clique com o botão direito do mouse na tarefa de inspeção no Gerenciador do Executor


de Tarefas e selecione Executar no menu de contexto. A janela de comando que mostra
a tarefa de inspeção em execução exibirá um "Aguardando..." Mensagem. Abra um dos
arquivos TypeScript, adicione um espaço e salve o arquivo. Isso disparará a tarefa de
inspeção e disparará as outras tarefas a serem executadas em ordem. A captura de tela
abaixo mostra uma execução de exemplo.
Associação a eventos do Visual Studio
A menos que você deseje iniciar manualmente suas tarefas sempre que trabalhar no
Visual Studio, associe tarefas a eventos Antes de Compilar, Após Compilar, Limpar e
Abrir Projeto .

Associe watch para que ele seja executado sempre que o Visual Studio for aberto. No
Gerenciador do Executor de Tarefas, clique com o botão direito do mouse na tarefa de
inspeção e selecione Projeto de Associações>Aberto no menu de contexto.

Descarregar e recarregar o projeto. Quando o projeto é carregado novamente, a tarefa


de inspeção começa a ser executada automaticamente.

Resumo
O Grunt é um executor de tarefas poderoso que pode ser usado para automatizar a
maioria das tarefas de build do cliente. O Grunt aproveita o NPM para entregar seus
pacotes e recursos de integração de ferramentas com o Visual Studio. O Gerenciador de
Executores 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.
Agrupar e minificar ativos estáticos em
ASP.NET Core
Artigo • 28/11/2022 • 4 minutos para o fim da leitura

Por Scott Addie e David Pine

Este artigo explica os benefícios da aplicação de agrupamento e minificação, incluindo


como esses recursos podem ser usados com ASP.NET Core aplicativos Web.

O que é agrupamento e minificação


Agrupamento e minificação são duas otimizações de desempenho distintas que você
pode aplicar em um aplicativo Web. Usados juntos, o agrupamento e a minificação
melhoram o desempenho reduzindo o número de solicitações de servidor e reduzindo o
tamanho dos ativos estáticos solicitados.

O agrupamento e a minificação melhoram principalmente o tempo de carregamento da


primeira solicitação de página. Depois que uma página da Web é solicitada, o
navegador armazena em cache os ativos estáticos (JavaScript, CSS e imagens). Portanto,
o agrupamento e a minificação não melhoram o desempenho ao solicitar a mesma
página, ou páginas, no mesmo site solicitando os mesmos ativos. Se o cabeçalho expirar
não estiver definido corretamente nos ativos e se o agrupamento e a minificação não
forem usados, a heurística de atualização do navegador marcará os ativos obsoletos
após alguns dias. Além disso, o navegador requer uma solicitação de validação para
cada ativo. Nesse caso, o agrupamento e a minificação fornecem uma melhoria de
desempenho mesmo após a primeira solicitação de página.

Agrupamento
O agrupamento combina vários arquivos em um único arquivo. O agrupamento reduz o
número de solicitações de servidor necessárias para renderizar um ativo da Web, como
uma página da Web. Você pode criar qualquer número de pacotes individuais
especificamente para CSS, JavaScript etc. Menos arquivos significam menos solicitações
HTTP do navegador para o servidor ou do serviço que fornece seu aplicativo. Isso
resulta em um melhor desempenho de carregamento da primeira página.

Minificação
A minificação remove caracteres desnecessários do código sem alterar a funcionalidade.
O resultado é uma redução significativa de tamanho nos ativos solicitados (como CSS,
imagens e arquivos JavaScript). Os efeitos colaterais comuns da minificação incluem
reduzir nomes de variáveis para um caractere e remover comentários e espaço em
branco desnecessário.

Considere a seguinte função JavaScript:

JavaScript

AddAltToImg = function (imageTagAndImageID, imageContext) {


///<signature>
///<summary> Adds an alt tab to the image
// </summary>
//<param name="imgElement" type="String">The image selector.</param>
//<param name="ContextForImage" type="String">The image context.</param>
///</signature>
var imageElement = $(imageTagAndImageID, imageContext);
imageElement.attr('alt', imageElement.attr('id').replace(/ID/, ''));
}

A minificação reduz a função para o seguinte:

JavaScript

AddAltToImg=function(t,a){var
r=$(t,a);r.attr("alt",r.attr("id").replace(/ID/,""))};

Além de remover os comentários e o 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

Impacto do agrupamento e da minificação


A tabela a seguir descreve as diferenças entre carregar ativos individualmente e usar o
agrupamento e a minificação para um aplicativo Web típico.

Ação Sem B/M Com B/M Redução


Ação Sem B/M Com B/M Redução

Solicitações de Arquivo 18 7 61%

Bytes transferidos (KB) 265 156 41%

Tempo de Carregamento (ms) 2360 885 63%

O tempo de carregamento melhorou, mas este exemplo foi executado localmente.


Maiores ganhos de desempenho são realizados ao usar o agrupamento e a minificação
com ativos transferidos por uma rede.

O aplicativo de teste usado para gerar os números na tabela anterior demonstra


melhorias típicas que podem não se aplicar a um determinado aplicativo.
Recomendamos testar um aplicativo para determinar se o agrupamento e a minificação
geram um tempo de carga aprimorado.

Escolher uma estratégia de agrupamento e


minificação
ASP.NET Core é compatível com o WebOptimizer, uma solução de agrupamento e
minificação de software livre. Para configurar instruções e projetos de exemplo, consulte
WebOptimizer . ASP.NET Core não fornece uma solução nativa de agrupamento e
minificação.

Ferramentas de terceiros, como Gulp e Webpack , fornecem automação de fluxo de


trabalho para agrupamento e minificação, bem como linting e otimização de imagem.
Usando o agrupamento e a minificação, os arquivos minificados são criados antes da
implantação do aplicativo. Agrupar e minificar antes da implantação fornece a vantagem
da carga reduzida do servidor. No entanto, é importante reconhecer que o
agrupamento e a minificação aumentam a complexidade do build e só funcionam com
arquivos estáticos.

Agrupamento e minificação baseados em


ambiente
Como prática recomendada, os arquivos empacotados e minificados do aplicativo
devem ser usados em um ambiente de produção. Durante o desenvolvimento, os
arquivos originais facilitam a depuração do aplicativo.
Especifique quais arquivos incluir em suas páginas usando o Auxiliar de Marca de
Ambiente em seus modos de exibição. O Auxiliar de Marca de Ambiente só renderiza
seu conteúdo ao executar em ambientes específicos.

A marca a seguir environment renderiza os arquivos CSS não processados durante a


execução no Development ambiente:

CSHTML

<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>

A marca a seguir environment renderiza os arquivos CSS empacotados e minificados ao


executar em um ambiente diferente de Development . Por exemplo, executar ou
Production Staging disparar a renderização dessas folhas de estilos:

CSHTML

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

Recursos adicionais
Usar vários ambientes
Auxiliares de Marcas
Link do navegador no ASP.NET Core
Artigo • 28/11/2022 • 4 minutos para o fim da leitura

Por Nicolò Carandini e Tom Dykstra

O Link do Navegador é um recurso do Visual Studio. Ele cria um canal de comunicação


entre o ambiente de desenvolvimento e um ou mais navegadores da Web. Use o Link
do Navegador para:

Atualize seu aplicativo Web em vários navegadores ao mesmo tempo.


Teste em vários navegadores com configurações específicas, como tamanhos de
tela.
Selecione elementos de interface do usuário em navegadores em tempo real, veja
a que marcação e origem ele está correlacionado no Visual Studio.
Realize a automação de teste do navegador em tempo real. O Link do Navegador
também é extensível.

Configuração do Link do Navegador


Adicione o pacote Microsoft.VisualStudio.Web.BrowserLink ao seu projeto. Para
ASP.NET Core Razor páginas ou projetos MVC, também habilite a compilação de Razor
arquivos ( .cshtml ) em runtime, conforme descrito na Razor compilação de arquivos em
ASP.NET Core. Razor As alterações de sintaxe são aplicadas somente quando a
compilação do runtime foi habilitada.

Configuração
Chame UseBrowserLink no método Startup.Configure :

C#

app.UseBrowserLink();

Normalmente UseBrowserLink , a chamada é colocada dentro de um if bloco que


habilita apenas o Link do Navegador no ambiente de desenvolvimento. Por exemplo:

C#

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

Para obter mais informações, consulte Usar vários ambientes no ASP.NET Core.

Como usar o Link do Navegador


Quando você tem um projeto de ASP.NET Core aberto, o Visual Studio mostra o
controle da barra de ferramentas do Link do Navegador ao lado do controle da barra de
ferramentas Depurar Destino:

No controle da barra de ferramentas do Link do Navegador, você pode:

Atualize o aplicativo Web em vários navegadores ao mesmo tempo.


Abra o Painel de Link do Navegador.
Habilitar ou desabilitar o Link do Navegador. Observação: o Link do Navegador
está desabilitado por padrão no Visual Studio.
Habilitar ou desabilitar a Sincronização Automática do CSS.

Atualizar o aplicativo Web em vários


navegadores ao mesmo tempo
Para escolher um único navegador da Web para iniciar ao iniciar o projeto, use o menu
suspenso no controle da barra de ferramentas Depurar Destino :

Para abrir vários navegadores ao mesmo tempo, escolha Procurar com... na mesma lista
suspensa. Mantenha pressionada a tecla Ctrl para selecionar os navegadores
desejados e clique em Procurar:

A captura de tela a seguir mostra o Visual Studio com a exibição Index aberta e dois
navegadores abertos:

Passe o mouse sobre o controle da barra de ferramentas do Browser Link para ver os
navegadores conectados ao projeto:
Altere o modo de exibição Índice e todos os navegadores conectados são atualizados
quando você clica no botão de atualização do Link do Navegador:

O Link do Navegador também funciona com navegadores que você inicia fora do Visual
Studio e navega até a URL do aplicativo.

Painel do Link do Navegador


Abra a janela Painel de Link do Navegador no menu suspenso Do Link do Navegador
para gerenciar a conexão com navegadores abertos:
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 cada navegador está mostrando:
Você também pode clicar em um nome de navegador individual para atualizar somente
esse navegador.

Habilitar ou desabilitar o Link do Navegador


Ao habilitar novamente o Link do Navegador depois de desabilitá-lo, você deve
atualizar os navegadores para reconectá-los.

Habilitar ou desabilitar a Sincronização Automática do


CSS
Quando a Sincronização Automática do CSS está habilitada, os navegadores conectados
são atualizados automaticamente quando você faz qualquer alteração nos arquivos CSS.

Como ele funciona


O Browser Link usa SignalR para criar um canal de comunicação entre o Visual Studio e
o navegador. Quando o Link do Navegador está habilitado, o Visual Studio atua como
um SignalR servidor ao qual vários clientes (navegadores) podem se conectar. O Link do
Navegador também registra um componente de middleware no pipeline de solicitação
ASP.NET Core. Esse componente injeta referências especiais <script> em cada
solicitação de página do servidor. Você pode ver as referências de script selecionando a
origem de exibição no navegador e rolando até o final do conteúdo da <body> marca:

HTML
<!-- Visual Studio Browser Link -->
<script type="application/json" id="__browserLink_initializationData">

{"requestId":"a717d5a07c1741949a7cefd6fa2bad08","requestMappingFromServer":f
alse}
</script>
<script type="text/javascript"
src="http://localhost:54139/b6e36e429d034f578ebccd6a79bf19bf/browserLink"
async="async"></script>
<!-- End Browser Link -->
</body>

Seus arquivos de origem não são modificados. O componente middleware injeta as


referências de script dinamicamente.

Como o código do lado do navegador é todo JavaScript, ele funciona em todos os


navegadores compatíveis SignalR sem a necessidade de um plug-in do navegador.
Gerenciamento de sessão e estado no
ASP.NET Core
Artigo • 28/11/2022 • 34 minutos para o fim da leitura

Por Rick Anderson , Kirk Larkin e Diana LaRose

O HTTP é um protocolo sem estado. Por padrão, as solicitações HTTP são mensagens
independentes que não retêm valores de usuário. Este artigo descreve várias
abordagens para preservar os dados do usuário entre solicitações.

Gerenciamento de estado
O estado pode ser armazenado usando várias abordagens. Cada abordagem é descrita
posteriormente neste tópico.

Abordagem de Mecanismo de armazenamento


armazenamento

Cookies HTTP cookies. Pode incluir dados armazenados usando o código do


aplicativo do lado do servidor.

Estado da sessão Http cookies e código de aplicativo do lado do servidor

TempData HTTP cookies ou estado de sessão

Cadeias de caracteres Cadeias de caracteres de consulta HTTP


de consulta

Campos ocultos Campos de formulário HTTP

HttpContext.Items Código do aplicativo do lado do servidor

Cache Código do aplicativo do lado do servidor

Cookies
Cookies armazenar dados entre solicitações. Como cookies são enviados com cada
solicitação, seu tamanho deve ser mantido ao mínimo. Idealmente, apenas um
identificador deve ser armazenado em um cookie com os dados armazenados pelo
aplicativo. A maioria dos navegadores restringe o cookie tamanho a 4096 bytes. Apenas
um número limitado de cookies está disponível para cada domínio.
Como cookieestão sujeitos a violação, eles devem ser validados pelo aplicativo. Cookies
podem ser excluídos pelos usuários e expirar em clientes. No entanto, cookies são
geralmente a forma mais durável de persistência de dados no cliente.

Cookiegeralmente são usados para personalização, em que o conteúdo é personalizado


para um usuário conhecido. O usuário é apenas identificado, e não autenticado, na
maioria dos casos. É cookie possível armazenar o nome do usuário, o nome da conta ou
a ID de usuário exclusiva, como um GUID. Ele cookie pode ser usado para acessar as
configurações personalizadas do usuário, como a cor da tela de fundo do site
preferencial.

Consulte o GDPR (Regulamento Geral de Proteção de Dados) da União Europeia ao


emitir cookies e lidar com preocupaçõ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.

Não há suporte para a sessão em SignalR aplicativos porque um SignalR Hub pode ser
executado independentemente 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.

ASP.NET Core mantém o estado da sessão fornecendo um cookie para o cliente que
contém uma ID de sessão. A cookie ID da sessão:

É enviado para o aplicativo com cada solicitação.


É usado pelo aplicativo para buscar os dados da sessão.

Estado de sessão exibe os seguintes comportamentos:

A sessão cookie é específica do navegador. As sessões não são compartilhadas


entre navegadores.
As sessões cookiesão excluídas quando a sessão do navegador termina.
Se um cookie for recebido para uma sessão expirada, será criada uma nova sessão
que usa a mesma sessão cookie.
As sessões vazias não são mantidas. A sessão deve ter pelo menos um valor
definido para persistir a sessão entre as 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.
O estado da sessão é ideal para armazenar dados do usuário:
Isso é específico para uma sessão específica.
Em que os dados não exigem armazenamento permanente entre sessões.
Os dados da sessão são excluídos quando a ISession.Clear implementação é
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 a sessão cookie é excluída ou
expirada no cliente.
Os estados cookieda sessão não são marcados como essenciais por padrão. O
estado da sessão não é funcional, a menos que o acompanhamento seja permitido
pelo visitante do site. Para obter mais informações, veja Suporte ao RGPD
(Regulamento Geral sobre a Proteção de Dados) no ASP.NET Core.
Observação: não há substituição para o cookierecurso de sessão menor do
ASP.NET Framework porque ele é considerado inseguro e pode levar a ataques de
correção de sessão.

2 Aviso

Não armazene dados confidenciais no estado de sessão. O usuário pode não fechar
o navegador e limpar a sessão cookie. Alguns navegadores mantêm sessões
cookieválidas entre janelas do navegador. Uma sessão pode não ser restrita a um
único usuário. O próximo usuário pode continuar navegando pelo aplicativo com a
mesma sessão cookie.

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 Cache distribuído em ASP.NET Core.
A sessão cookie é criptografada por meio de IDataProtector. A Proteção de Dados
deve ser configurada corretamente para ler as sessões cookieem cada
computador. Para obter mais informações, consulte ASP.NET Core Visão geral da
Proteção de Dados e provedores de armazenamento de chaves.

Configurar o estado de sessão


O pacote Microsoft.AspNetCore.Session :

É incluído implicitamente pela estrutura.


Fornece middleware para gerenciar o estado da sessão.

Para habilitar o middleware da sessão, Program.cs deve conter:

Qualquer um dos IDistributedCache caches de memória. A implementação


IDistributedCache é usada como um repositório de backup para a sessão. Para

obter mais informações, consulte Cache distribuído em ASP.NET Core.


Uma chamada para AddSession
Uma chamada para UseSession

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 :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseSession();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

O código anterior define um curto tempo limite para simplificar o teste.

A ordem do middleware é importante. Ligue UseSession depois UseRouting e antes


MapRazorPages e MapDefaultControllerRoute ... Consulte o Middleware Ordering.

HttpContext.Session estará disponível depois que o estado de sessão for configurado.

HttpContext.Session não pode ser acessado antes que UseSession tenha sido chamado.

Uma nova sessão com uma nova sessão cookie não pode ser criada depois que o
aplicativo começar a gravar no fluxo de resposta. A exceção é registrada no log do
servidor Web e não é exibida no navegador.

Carregue o estado de sessão de maneira assíncrona


O provedor de sessão padrão em ASP.NET Core carrega registros de sessão do
repositório IDistributedCache de backup subjacente de forma assíncrona somente se o
ISession.LoadAsync método for chamado explicitamente antes do TryGetValue, Setou
Remove métodos. Se LoadAsync não for chamado primeiro, o registro de sessão
subjacente será carregado de maneira síncrona, o que pode incorrer em uma
penalidade de desempenho em escala.

Para que os aplicativos imponham esse padrão, encapsule e


DistributedSessionStoreDistributedSession implemente com versões que geram uma
exceção se o LoadAsync método não for chamado antes TryGetValue , Set ou Remove .
Registre as versões encapsuladas no contêiner de serviços.

Opções da sessão
Para substituir os padrões de sessão, use SessionOptions.

Opção Descrição
Opção Descrição

Cookie Determina as configurações usadas para criar o cookie.


NameSessionDefaults.CookieName o padrão é ( .AspNetCore.Session ).
PathSessionDefaults.CookiePath o padrão é ( / ). SameSiteSameSiteMode.Lax o
padrão é ( 1 ). HttpOnly usa como padrão true . IsEssential usa como padrão false .

IdleTimeout O IdleTimeout indica por quanto tempo a sessão pode ficar ociosa antes de seu
conteúdo ser abandonado. Cada acesso à sessão redefine o tempo limite. Essa
configuração só se aplica ao conteúdo da sessão, não ao cookie. O padrão é de 20
minutos.

IOTimeout O tempo máximo permitido para carregar uma sessão do repositório ou para
confirmá-la de volta para o repositório. Essa configuração pode se aplicar somente
a operações assíncronas. Esse tempo limite pode ser desabilitado usando
InfiniteTimeSpan. O padrão é 1 minuto.

A sessão usa um cookie para rastrear e identificar solicitações de um único navegador.


Por padrão, isso cookie é nomeado .AspNetCore.Session e usa um caminho de / . Como
o cookie padrão não especifica um domínio, ele não é disponibilizado para o script do
lado do cliente na página (porque HttpOnly o padrão true é ).

Para substituir cookie os padrões de sessão, use SessionOptions:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
options.Cookie.Name = ".AdventureWorks.Session";
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.IsEssential = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthorization();

app.UseSession();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

O aplicativo usa a IdleTimeout propriedade para determinar por quanto tempo uma
sessão pode ficar ociosa antes que seu conteúdo no cache do servidor seja
abandonado. Essa propriedade é independente da cookie expiração. 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


O estado da sessão é acessado de uma Razor classe Pages PageModel ou classe MVC
Controller com HttpContext.Session. Essa propriedade é uma implementação 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
Microsoft.AspNetCore.Http namespace.

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 IndexModel.SessionKeyName chave


( _Name no aplicativo de exemplo) em uma Razor página Páginas:

C#
@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:

C#

public class IndexModel : PageModel


{
public const string SessionKeyName = "_Name";
public const string SessionKeyAge = "_Age";

private readonly ILogger<IndexModel> _logger;

public IndexModel(ILogger<IndexModel> logger)


{
_logger = logger;
}

public void OnGet()


{
if
(string.IsNullOrEmpty(HttpContext.Session.GetString(SessionKeyName)))
{
HttpContext.Session.SetString(SessionKeyName, "The Doctor");
HttpContext.Session.SetInt32(SessionKeyAge, 73);
}
var name = HttpContext.Session.GetString(SessionKeyName);
var age = HttpContext.Session.GetInt32(SessionKeyAge).ToString();

_logger.LogInformation("Session Name: {Name}", name);


_logger.LogInformation("Session Age: {Age}", age);
}
}

A marcação a seguir exibe os valores de sessão em uma Razor página:

CSHTML

@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<div class="text-center">
<p><b>Name:</b> @HttpContext.Session.GetString("_Name");<b>Age:

</b> @HttpContext.Session.GetInt32("_Age").ToString()</p>
</div>

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. Serializadores de cadeia de caracteres
e inteiros são fornecidos pelos métodos de extensão de ISession. Tipos complexos
devem ser serializados pelo usuário usando outro mecanismo, como JSON.

Use o seguinte código de exemplo para serializar objetos:

C#

public static class SessionExtensions


{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}

public static T? Get<T>(this ISession session, string key)


{
var value = session.GetString(key);
return value == null ? default : JsonSerializer.Deserialize<T>
(value);
}
}

O exemplo a seguir mostra como definir e obter um objeto serializável com a


SessionExtensions classe:

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using Web.Extensions; // SessionExtensions

namespace SessionSample.Pages
{
public class Index6Model : PageModel
{
const string SessionKeyTime = "_Time";
public string? SessionInfo_SessionTime { get; private set; }
private readonly ILogger<Index6Model> _logger;

public Index6Model(ILogger<Index6Model> logger)


{
_logger = logger;
}

public void OnGet()


{
var currentTime = DateTime.Now;

// Requires SessionExtensions from sample.


if (HttpContext.Session.Get<DateTime>(SessionKeyTime) ==
default)
{
HttpContext.Session.Set<DateTime>(SessionKeyTime,
currentTime);
}
_logger.LogInformation("Current Time: {Time}", currentTime);
_logger.LogInformation("Session Time: {Time}",
HttpContext.Session.Get<DateTime>
(SessionKeyTime));

}
}
}

TempData
ASP.NET Core expõe o Razor Páginas TempData ou ControladorTempData. Essa
propriedade armazena dados até que sejam lidos em outra solicitação. Os métodos
Keep(String) e Peek(string) podem ser usados para examinar os dados sem exclusão no
final da solicitação. Mantenha marcas em todos os itens no dicionário de retenção.
TempData é:

Útil para redirecionamento quando os dados são necessários para mais de uma
única solicitação.
Implementado por TempData provedores usando o cookieestado de sessão ou s.

Exemplos de TempData
Considere a página a seguir que cria um cliente:

C#

public class CreateModel : PageModel


{
private readonly RazorPagesContactsContext _context;

public CreateModel(RazorPagesContactsContext context)


{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

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

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

public async Task<IActionResult> OnPostAsync()


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

_context.Customer.Add(Customer);
await _context.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";

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

A página a seguir exibe TempData["Message"] :

CSHTML

@page
@model IndexModel

<h1>Peek Contacts</h1>

@{
if (TempData.Peek("Message") != null)
{
<h3>Message: @TempData.Peek("Message")</h3>
}
}

@*Content removed for brevity.*@

Na marcação anterior, no final da solicitação, TempData["Message"] não é excluído


porque Peek é usado. Atualizar a página exibe o conteúdo de TempData["Message"] .
A marcação a seguir é semelhante ao código anterior, mas usa Keep para preservar os
dados no final da solicitação:

CSHTML

@page
@model IndexModel

<h1>Contacts Keep</h1>

@{
if (TempData["Message"] != null)
{
<h3>Message: @TempData["Message"]</h3>
}
TempData.Keep("Message");
}

@*Content removed for brevity.*@

Navegar entre as páginas IndexPeek e IndexKeep não excluirá TempData["Message"] .

O código a seguir é TempData["Message"] exibido, mas no final da solicitação,


TempData["Message"] é excluído:

CSHTML

@page
@model IndexModel

<h1>Index no Keep or Peek</h1>

@{
if (TempData["Message"] != null)
{
<h3>Message: @TempData["Message"]</h3>
}
}

@*Content removed for brevity.*@

Provedores de TempData
O cookieprovedor TempData baseado é usado por padrão para armazenar TempData
em cookies.
Os cookie dados são criptografados usando IDataProtector, codificados com
Base64UrlTextEncoder, em seguida, em partes. O tamanho máximo cookie é menor que
4.096 bytes devido à criptografia e ao agrupamento. Os cookie dados não são
compactados porque a compactação de dados criptografados pode levar a problemas
de segurança, como ataques CRIME e BREACH ataques. Para obter mais
informações sobre o cookieprovedor TempData baseado, consulte
CookieTempDataProvider.

Escolha um provedor de TempData


Escolher um provedor de TempData envolve várias considerações, como:

O aplicativo já usa estado de sessão? Nesse caso, usar o provedor TempData do


estado de sessão não terá nenhum custo adicional para o aplicativo além do
tamanho dos dados.
O aplicativo usa TempData apenas com moderação para quantidades
relativamente pequenas de dados, até 500 bytes? Nesse caso, o cookie provedor
TempData adiciona um pequeno custo a cada solicitação que carrega 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.
O aplicativo é executado em um farm de servidores em vários servidores? Nesse
caso, não há nenhuma configuração adicional necessária para usar o cookie
provedor TempData fora da Proteção de Dados. Para obter mais informações,
consulte ASP.NET Core Visão geral da Proteção de Dados e provedores de
armazenamento de chaves.

A maioria dos clientes Web, como navegadores da Web, impõe limites ao tamanho
máximo de cada cookie um e ao número total de cookies. Ao usar o cookie provedor
TempData, verifique se o aplicativo não excederá esses limites . Considere o tamanho
total dos dados. Contabilize aumentos de cookie tamanho devido à criptografia e ao
agrupamento.

Configurar o provedor de TempData


O cookieprovedor TempData baseado está habilitado por padrão.

Para habilitar o provedor TempData baseado em sessão, use o


AddSessionStateTempDataProvider método de extensão. Somente uma chamada é
AddSessionStateTempDataProvider necessária:

C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages()
.AddSessionStateTempDataProvider();
builder.Services.AddControllersWithViews()
.AddSessionStateTempDataProvider();

builder.Services.AddSession();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseSession();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

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 do compartilhamento não intencional, a inclusão de dados em cadeias de


caracteres de consulta pode expor o aplicativo a ataques CSRF (Solicitação forjada
entre sites). Qualquer estado de sessão preservado deve proteger contra ataques
CSRF. Para obter mais informações, consulte Impedir ataques de XSRF/CSRF (solicitação
intersite forjada) no ASP.NET Core.

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 HttpContext.Items coleção é 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, o middleware adiciona isVerified à Items coleção:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

ILogger logger = app.Logger;

app.Use(async (context, next) =>


{
// context.Items["isVerified"] is null
logger.LogInformation($"Before setting: Verified:
{context.Items["isVerified"]}");
context.Items["isVerified"] = true;
await next.Invoke();
});

app.Use(async (context, next) =>


{
// context.Items["isVerified"] is true
logger.LogInformation($"Next: Verified: {context.Items["isVerified"]}");
await next.Invoke();
});

app.MapGet("/", async context =>


{
await context.Response.WriteAsync($"Verified:
{context.Items["isVerified"]}");
});

app.Run();

Para o middleware usado apenas em um único aplicativo, é improvável que o uso de


uma chave fixa string cause uma colisão de chave. No entanto, para evitar a
possibilidade de uma colisão de chave completamente, um object pode ser usado
como uma chave de item. Essa abordagem é particularmente útil para o middleware
compartilhado entre aplicativos e também tem a vantagem de eliminar o uso de cadeias
de caracteres de chave no código. O exemplo a seguir mostra como usar uma object
chave definida em uma classe de middleware:

C#

public class HttpContextItemsMiddleware


{
private readonly RequestDelegate _next;
public static readonly object HttpContextItemsMiddlewareKey = new();

public HttpContextItemsMiddleware(RequestDelegate next)


{
_next = next;
}

public async Task Invoke(HttpContext httpContext)


{
httpContext.Items[HttpContextItemsMiddlewareKey] = "K-9";

await _next(httpContext);
}
}

public static class HttpContextItemsMiddlewareExtensions


{
public static IApplicationBuilder
UseHttpContextItemsMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<HttpContextItemsMiddleware>();
}
}

Outros códigos podem acessar o valor armazenado em HttpContext.Items usando a


chave exposta pela classe do middleware:

C#

public class Index2Model : PageModel


{
private readonly ILogger<Index2Model> _logger;

public Index2Model(ILogger<Index2Model> logger)


{
_logger = logger;
}

public void OnGet()


{
HttpContext.Items

.TryGetValue(HttpContextItemsMiddleware.HttpContextItemsMiddlewareKey,
out var middlewareSetValue);

_logger.LogInformation("Middleware value {MV}",


middlewareSetValue?.ToString() ?? "Middleware value not set!");
}
}

Cache
O cache é uma maneira eficiente de armazenar e recuperar dados. O aplicativo pode
controlar o tempo de vida de itens em cache. Para obter mais informações, consulte o
cache de resposta em ASP.NET Core.

Dados armazenados em cache não são associados uma solicitação, usuário ou sessão
específico. Não armazene em cache dados específicos do usuário que possam ser
recuperados por outras solicitações de usuário.

Para armazenar dados em todo o aplicativo em cache, consulte Cache na memória em


ASP.NET Core.

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

Normalmente, isso é causado por não configurar pelo menos uma


IDistributedCache implementação. Para obter mais informações, consulte Cache

distribuído em ASP.NET Core e cache na memória em ASP.NET Core.

Se o middleware de sessão não persistir uma sessão:

O middleware registra a exceção e a solicitação continua normalmente.


Isso leva a um comportamento imprevisível.

O middleware de sessão poderá falhar ao persistir uma sessão se o repositório de


backup não estiver disponí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 quando o aplicativo terminar 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 é gerado sob as
mesmas condições quando o armazenamento de dados não está disponível.

SignalR e estado da sessão


SignalR os aplicativos não devem usar o estado da sessão para armazenar informações.
SignalR os aplicativos podem armazenar por estado de conexão no Context.Items hub.

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

Hospedar o ASP.NET Core em um web farm


Layout no ASP.NET Core
Artigo • 04/01/2023 • 6 minutos para o fim da leitura

Por Steve Smith e Dave Brock

Páginas e exibições com frequência compartilham elementos visuais e programáticos.


Este artigo demonstra como:

Usar layouts comuns.


Compartilhar diretivas.
Executar o código comum antes de renderizar páginas ou modos de exibição.

Este documento discute layouts para as duas abordagens diferentes para ASP.NET Core
MVC: Razor páginas e controladores com exibições. Para este tópico, as diferenças são
mínimas:

Razor As páginas estão na pasta Páginas .


Controladores com exibições usam uma pasta Views pasta exibições.

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
frequentemente por muitas páginas em um aplicativo. Todos esses elementos
compartilhados podem ser definidos em um arquivo de layout , que pode ser
referenciado por qualquer exibição usada no aplicativo. Os layouts reduzem o código
duplicado nas exibições.

Por convenção, o layout padrão de um aplicativo ASP.NET Core é chamado


_Layout.cshtml . Os arquivos de layout para novos projetos do ASP.NET Core criados
com os modelos são:

Razor Páginas: Pages/Shared/_Layout.cshtml

Controlador com exibições: Views/Shared/_Layout.cshtml

O layout define um modelo de nível superior para exibições no aplicativo. Aplicativos


não exigem um layout. Os aplicativos podem definir mais de um layout, com diferentes
exibições que especificam layouts diferentes.

O código a seguir mostra o arquivo de layout para um modelo de projeto criado com
um controlador e exibições:

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

<partial name="_CookieConsentPartial" />

<div class="container body-content">


@RenderBody()
<hr />
<footer>
<p>&copy; 2018 - WebApplication1</p>
</footer>
</div>

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

@RenderSection("Scripts", required: false)


</body>
</html>

Especificando um layout
Razor os modos de exibição têm uma Layout propriedade. As exibições individuais
especificam um layout com a configuração dessa propriedade:

CSHTML

@{
Layout = "_Layout";
}

O layout especificado pode usar um caminho completo (por exemplo,


/Pages/Shared/_Layout.cshtml ou) ou /Views/Shared/_Layout.cshtml um nome parcial
(exemplo: _Layout ). Quando um nome parcial é fornecido, o mecanismo de exibição
Razor pesquisa o arquivo de layout usando seu processo de descoberta padrão. A pasta
em que o método do manipulador (ou controlador) existe é pesquisada primeiro,
seguida pela pasta Shared. Esse processo de descoberta é idêntico àquele usado para
descobrir exibições parciais.

Por padrão, todo layout precisa chamar RenderBody . Sempre que a chamada a
RenderBody for feita, o conteúdo da exibição será renderizado.

Seções
Um layout, opcionalmente, pode referenciar uma ou mais seções, chamando
RenderSection . As seções fornecem uma maneira de organizar o local em que
determinados elementos da página devem ser colocados. Cada chamada a
RenderSection pode especificar se essa seção é obrigatória ou opcional:

HTML

<script type="text/javascript" src="~/scripts/global.js"></script>

@RenderSection("Scripts", required: false)

Se uma seção obrigatória não for encontrada, uma exceção será gerada. Exibições
individuais especificam o conteúdo a ser renderizado em uma seção usando a
@section Razor sintaxe. Se uma página ou exibição definir uma seção, ela precisará ser
renderizada (ou ocorrerá um erro).

Uma definição de exemplo @section no modo Razor páginas:

HTML

@section Scripts {
<script type="text/javascript" src="~/scripts/main.js"></script>
}

No código anterior, scripts/main.js é adicionado à scripts seção 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 :

HTML

@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
A marcação anterior foi gerada por scaffolding Identity.

As seções definidas em uma página ou exibição estão disponíveis apenas em sua página
de layout imediata. Elas não podem ser referenciadas em parciais, componentes de
exibição ou outras partes do sistema de exibição.

Ignorando seções
Por padrão, o corpo e todas as seções de uma página de conteúdo precisam ser
renderizados pela página de layout. O Razor mecanismo de exibição impõe isso
rastreando se o corpo e cada seção foram renderizados.

Para instruir o mecanismo de exibição a ignorar o corpo ou as seções, chame os


métodos IgnoreBody e IgnoreSection .

O corpo e cada seção em uma Razor página devem ser renderizados ou ignorados.

Importando diretivas compartilhadas


Exibições e páginas podem usar Razor diretivas para importar namespaces e usar
injeção de dependência. Diretivas compartilhadas por muitas exibições podem ser
especificadas em um arquivo _ViewImports.cshtml comum. O arquivo _ViewImports dá
suporte às seguintes diretivas:

@addTagHelper

@removeTagHelper
@tagHelperPrefix

@using
@model

@inherits

@inject
@namespace

O arquivo não dá suporte a outros Razor recursos, como funções e definições de seção.

Um arquivo _ViewImports.cshtml de exemplo:

CSHTML

@using WebApplication1
@using WebApplication1.Models
@using WebApplication1.Models.AccountViewModels
@using WebApplication1.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

O _ViewImports.cshtml arquivo de um aplicativo MVC ASP.NET Core normalmente é


colocado na pasta Páginas (ou Exibições). Um _ViewImports.cshtml arquivo pode ser
colocado dentro de qualquer pasta, caso em que ele só será aplicado a páginas ou
exibições dentro dessa pasta e 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 de nível _ViewImports.cshtml raiz inclui @model MyModel1 e


@addTagHelper *, MyTagHelper1 .

Um arquivo de subpasta _ViewImports.cshtml inclui @model MyModel2 e


@addTagHelper *, MyTagHelper2 .

Páginas e exibições na subpasta terão acesso a Auxiliares de Marca e ao modelo


MyModel2 .

Se vários _ViewImports.cshtml arquivos forem encontrados na hierarquia de arquivos, o


comportamento combinado das diretivas será:

@addTagHelper , @removeTagHelper : todos são executados, em ordem

@tagHelperPrefix : o mais próximo à exibição substitui todos os outros

@model : o mais próximo à exibição substitui todos os outros


@inherits : o mais próximo à exibição substitui todos os outros

@using : todos são incluídos; duplicatas são ignoradas


@inject : para cada propriedade, o mais próximo à exibição substitui todos os

outros com o mesmo nome de propriedade

Executando o código antes de cada exibição


Código que precisa ser executado antes que cada exibição ou página seja colocada no
_ViewStart.cshtml arquivo. Por convenção, o _ViewStart.cshtml arquivo está localizado
na pasta Páginas (ou Exibições). As instruções listadas em _ViewStart.cshtml são
executadas antes de cada exibição completa (não layouts nem exibições parciais). Como
ViewImports.cshtml, _ViewStart.cshtml é hierárquico. Se um _ViewStart.cshtml arquivo
for definido na pasta exibição ou páginas, ele será executado após o definido na raiz da
pasta Páginas (ou Exibições) (se houver).

Um arquivo _ViewStart.cshtml de exemplo:

CSHTML

@{
Layout = "_Layout";
}

O arquivo acima especifica que todas as exibições usarão o layout _Layout.cshtml .

_ViewStart.cshtml e _ViewImports.cshtml normalmente não são colocados na pasta

/Pages/Shared (ou /Views/Shared). As versões no nível do aplicativo desses arquivos


devem ser colocadas diretamente na pasta /Pages (ou /Views).
Razor Referência de sintaxe para
ASP.NET Core
Artigo • 28/11/2022 • 21 minutos para o fim da leitura

Por Rick Anderson , Taylor Mullen e Dan Vicarel

Razor é uma sintaxe de marcação para inserir código baseado em .NET em páginas da
Web. A Razor sintaxe consiste em Razor marcação, C#e HTML. Os arquivos que contêm
Razor geralmente têm uma .cshtml extensão de arquivo. Razortambém é encontrado
em Razor arquivos de componente ( .razor ). RazorA sintaxe é semelhante aos
mecanismos de modelagem de várias estruturas de SPA (aplicativo de página única)
JavaScript, como Angular, React, VueJs e Svelte. Para obter mais informações, veja como
usar os Serviços JavaScript para criar aplicativos de página única no ASP.NET Core.

Introdução à programação da Web ASP.NET usando o Razor A sintaxe fornece muitos


exemplos de programação com Razor sintaxe. Embora o tópico tenha sido escrito para
ASP.NET em vez de ASP.NET Core, a maioria dos exemplos se aplicam a ASP.NET Core.

Renderização de HTML
O idioma padrão Razor é HTML. Renderizar HTML da Razor marcação não é diferente de
renderizar HTML de um arquivo HTML. A marcação HTML em .cshtml Razor arquivos é
renderizada pelo servidor inalterada.

Sintaxe de Razor
Razor dá suporte a C# e usa o símbolo para fazer a @ transição de HTML para C#. Razor
avalia as expressões C# e as renderiza na saída HTML.

Quando um @ símbolo é seguido por uma Razor palavra-chave reservada, ele faz a
transição para Razoruma marcação específica. Caso contrário, ele faz a transição para
HTML sem formatação.

Para escapar de um @ símbolo na Razor marcação, use um segundo @ símbolo:

CSHTML

<p>@@Username</p>

O código é renderizado em HTML com um único símbolo @ :


HTML

<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 são
intocados pela Razor análise:

CSHTML

<a href="mailto:Support@contoso.com">Support@contoso.com</a>

Scalable Vector Graphics (SVG)


Há suporte para elementos SVG foreignObject :

HTML

@{
string message = "foreignObject example with Scalable Vector Graphics
(SVG)";
}

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">


<rect x="0" y="0" rx="10" ry="10" width="200" height="200"
stroke="black"
fill="none" />
<foreignObject x="20" y="20" width="160" height="160">
<p>@message</p>
</foreignObject>
</svg>

Expressões implícitas Razor


Expressões implícitas Razor começam com @ o código C# seguido:

CSHTML

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

<p>@await DoSomething("hello", "world")</p>

Expressões implícitas não podem conter elementos genéricos de C#, pois caracteres
dentro de colchetes ( <> ) são interpretados como uma marca HTML. O código a seguir é
inválido:

CSHTML

<p>@GenericMethod<int>()</p>

O código anterior gera um erro de compilador semelhante a um dos seguintes:

O elemento "int" não foi fechado. Todos os elementos devem ter fechamento
automático ou ter uma marca de fim correspondente.
Não é possível converter o grupo de métodos "GenericMethod" em um "object"
de tipo não delegado. Você pretendia invocar o método?

Chamadas de método genérico devem ser encapsuladas em uma expressão explícita


Razor ou um bloco de Razor código.

Expressões explícitas Razor


Expressões explícitas Razor consistem em um @ símbolo com parêntese equilibrado.
Para renderizar a hora da semana passada, a seguinte Razor marcação é usada:

CSHTML

<p>Last week this time: @(DateTime.Now - TimeSpan.FromDays(7))</p>

Qualquer conteúdo dentro dos parênteses @() é avaliado e renderizado para a saída.

Expressões implícitas, descritas na seção anterior, geralmente não podem conter


espaços. No código a seguir, uma semana não é subtraída da hora atual:

CSHTML

<p>Last week: @DateTime.Now - TimeSpan.FromDays(7)</p>

O código renderiza o HTML a seguir:

HTML
<p>Last week: 7/7/2016 4:39:52 PM - TimeSpan.FromDays(7)</p>

Expressões explícitas podem ser usadas para concatenar texto com um resultado de
expressão:

CSHTML

@{
var joe = new Person("Joe", 33);
}

<p>Age@(joe.Age)</p>

Sem a expressão explícita, <p>Age@joe.Age</p> é tratado como um endereço de email e


<p>Age@joe.Age</p> é renderizado. Quando escrito como uma expressão explícita,

<p>Age33</p> é renderizado.

Expressões explícitas podem ser usadas para renderizar a saída de métodos genéricos
em .cshtml arquivos. A marcação a seguir mostra como corrigir o erro mostrado
anteriormente causado pelos colchetes de um C# genérico. O código é escrito como
uma expressão explícita:

CSHTML

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

CSHTML

@("<span>Hello World</span>")

O código anterior renderiza o seguinte HTML:

HTML
&lt;span&gt;Hello World&lt;/span&gt;

O HTML é mostrado no navegador como texto sem formatação:

<span> Olá, Mundo</span>

A saída HtmlHelper.Raw não é codificada, mas renderizada como marcação HTML.

2 Aviso

Usar HtmlHelper.Raw em uma entrada do usuário que não está limpa é um risco de
segurança. A entrada do usuário pode conter JavaScript mal-intencionado ou
outras formas de exploração. Limpar a entrada do usuário é difícil. Evite usar
HtmlHelper.Raw com a entrada do usuário.

CSHTML

@Html.Raw("<span>Hello World</span>")

O código renderiza o HTML a seguir:

HTML

<span>Hello World</span>

Razor blocos de código


Razor blocos de código começam com @ e são colocados por {} . Diferente das
expressões, o código C# dentro de blocos de código não é renderizado. Blocos de
código e expressões em uma exibição compartilham o mesmo escopo e são definidos
em ordem:

CSHTML

@{
var quote = "The future depends on what you do today. - Mahatma Gandhi";
}

<p>@quote</p>

@{
quote = "Hate cannot drive out hate, only love can do that. - Martin
Luther King, Jr.";
}

<p>@quote</p>

O código renderiza o HTML a seguir:

HTML

<p>The future depends on what you do today. - Mahatma Gandhi</p>


<p>Hate cannot drive out hate, only love can do that. - Martin Luther King,
Jr.</p>

Em blocos de código, declare funções locais com uma marcação para servir como
métodos de modelagem:

CSHTML

@{
void RenderName(string name)
{
<p>Name: <strong>@name</strong></p>
}

RenderName("Mahatma Gandhi");
RenderName("Martin Luther King, Jr.");
}

O código renderiza o HTML a seguir:

HTML

<p>Name: <strong>Mahatma Gandhi</strong></p>


<p>Name: <strong>Martin Luther King, Jr.</strong></p>

Transições implícitas
O idioma padrão em um bloco de código é C#, mas a Página pode fazer a Razor
transição de volta para HTML:

CSHTML

@{
var inCSharp = true;
<p>Now in HTML, was in C# @inCSharp</p>
}
Transição delimitada explícita
Para definir uma subseção de um bloco de código que deve renderizar HTML, cerque os
caracteres para renderização com a Razor <text> marca:

CSHTML

@for (var i = 0; i < people.Length; i++)


{
var person = people[i];
<text>Name: @person.Name</text>
}

Use essa abordagem para renderizar HTML que não está circundado por uma marca
HTML. Sem um HTML ou Razor marca, ocorre um Razor erro de runtime.

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


Para renderizar o restante de uma linha inteira como HTML dentro de um bloco de
código, use @: a sintaxe:

CSHTML

@for (var i = 0; i < people.Length; i++)


{
var person = people[i];
@:Name: @person.Name
}

Sem o @: código, um Razor erro de runtime é gerado.

Caracteres extras @ em um Razor arquivo podem causar erros de compilador em


instruções posteriormente no bloco. Esses erros do compilador podem ser difíceis de
entender porque o erro real ocorre antes do erro relatado. Esse erro é comum após
combinar várias expressões implícitas/explícitas em um bloco de código único.

Estruturas de controle
Estruturas de controle são uma extensão dos blocos de código. Todos os aspectos dos
blocos de código (transição para marcação, C# embutido) também se aplicam às
seguintes estruturas:

Condicionais @if, else if, else, and @switch


@if controla quando o código é executado:

CSHTML

@if (value % 2 == 0)
{
<p>The value was even.</p>
}

else e else if não exigem o símbolo @ :

CSHTML

@if (value % 2 == 0)
{
<p>The value was even.</p>
}
else if (value >= 1337)
{
<p>The value is large.</p>
}
else
{
<p>The value is odd and small.</p>
}

A marcação a seguir mostra como usar uma instrução switch:

CSHTML

@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;
}
Looping @for, @foreach, @while, and @do while
O HTML no modelo pode ser renderizado com instruções de controle em loop. Para
renderizar uma lista de pessoas:

CSHTML

@{
var people = new Person[]
{
new Person("Weston", 33),
new Person("Johnathon", 41),
...
};
}

Há suporte para as seguintes instruções em loop:

@for

CSHTML

@for (var i = 0; i < people.Length; i++)


{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>
}

@foreach

CSHTML

@foreach (var person in people)


{
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>
}

@while

CSHTML

@{ var i = 0; }
@while (i < people.Length)
{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>

i++;
}

@do while

CSHTML

@{ 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. Em
Razor, o mesmo mecanismo é usado para criar auxiliares HTML que contêm conteúdo
adicional. No código a seguir, os Auxiliares HTML renderizam uma marca <form> com a
instrução @using :

CSHTML

@using (Html.BeginForm())
{
<div>
Email: <input type="email" id="Email" value="">
<button>Register</button>
</div>
}

@try, catch, finally

O tratamento de exceções é semelhante ao de C#:

CSHTML

@try
{
throw new InvalidOperationException("You did something invalid.");
}
catch (Exception ex)
{
<p>The exception message: @ex.Message</p>
}
finally
{
<p>The finally statement.</p>
}

@lock

Razor tem a capacidade de proteger seções críticas com instruções de bloqueio:

CSHTML

@lock (SomeLock)
{
// Do critical section work
}

Comentários
Razor dá suporte a comentários em C# e HTML:

CSHTML

@{
/* C# comment */
// Another C# comment
}
<!-- HTML comment -->

O código renderiza o HTML a seguir:

HTML

<!-- HTML comment -->

Razor os comentários são removidos pelo servidor antes que a página da Web seja
renderizada. Razor usa @* *@ para delimitar comentários. O código a seguir é
comentado, de modo que o servidor não renderiza nenhuma marcação:

CSHTML
@*
@{
/* C# comment */
// Another C# comment
}
<!-- HTML comment -->
*@

Diretivas
Razor as diretivas 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.

Entender como Razor gera código para uma exibição torna mais fácil entender como as
diretivas funcionam.

CSHTML

@{
var quote = "Getting old ain't for wimps! - Anonymous";
}

<div>Quote of the Day: @quote</div>

O código gera uma classe semelhante à seguinte:

C#

public class _Views_Something_cshtml : RazorPage<dynamic>


{
public override async Task ExecuteAsync()
{
var output = "Getting old ain't for wimps! - Anonymous";

WriteLiteral("/r/n<div>Quote of the Day: ");


Write(output);
WriteLiteral("</div>");
}
}

Posteriormente neste artigo, a seção Inspecionar a Razor classe C# gerada para um


modo de exibição explica como exibir essa classe gerada.

@attribute
A diretiva @attribute adiciona o atributo fornecido à classe da página ou exibição
gerada. O exemplo a seguir adiciona o atributo [Authorize] :

CSHTML

@attribute [Authorize]

A @attribute diretiva também pode ser usada para fornecer um modelo de rota
baseado em constantes em um Razor componente. No exemplo a seguir, a @page
diretiva em um componente é substituída pela @attribute diretiva e pelo modelo de
rota baseado em constante, Constants.CounterRoute que é definido em outro lugar no
aplicativo como " /counter ":

diff

- @page "/counter"
+ @attribute [Route(Constants.CounterRoute)]

@code

Esse cenário só se aplica a Razor componentes ( .razor ).

O @code bloco permite que um Razor componente adicione membros C# (campos,


propriedades e métodos) a um componente:

razor

@code {
// C# members (fields, properties, and methods)
}

Para Razor componentes, @code é um alias de @functions e recomendado sobre


@functions . Mais de um bloco de @code é permitido.

@functions

A diretiva @functions permite adicionar membros (campos, propriedades e métodos) de


C# à classe gerada:

CSHTML

@functions {
// C# members (fields, properties, and methods)
}

Em Razor componentes, use @code mais @functions para adicionar membros C#.

Por exemplo:

CSHTML

@functions {
public string GetHello()
{
return "Hello";
}
}

<div>From method: @GetHello()</div>

O código gera a seguinte marcação HTML:

HTML

<div>From method: Hello</div>

O código a seguir é a classe C# gerada Razor :

C#

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;

public class _Views_Home_Test_cshtml : RazorPage<dynamic>


{
// Functions placed between here
public string GetHello()
{
return "Hello";
}
// And here.
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
WriteLiteral("\r\n<div>From method: ");
Write(GetHello());
WriteLiteral("</div>\r\n");
}
#pragma warning restore 1998

Os métodos @functions servem como métodos de modelagem quando têm uma


marcação:
CSHTML

@{
RenderName("Mahatma Gandhi");
RenderName("Martin Luther King, Jr.");
}

@functions {
private void RenderName(string name)
{
<p>Name: <strong>@name</strong></p>
}
}

O código renderiza o HTML a seguir:

HTML

<p>Name: <strong>Mahatma Gandhi</strong></p>


<p>Name: <strong>Martin Luther King, Jr.</strong></p>

@implements

A diretiva @implements implementa uma interface para a classe gerada.

O exemplo a seguir implementa System.IDisposable para que o método Dispose possa


ser chamado:

CSHTML

@implements IDisposable

<h1>Example</h1>

@functions {
private bool _isDisposed;

...

public void Dispose() => _isDisposed = true;


}

@inherits

A diretiva @inherits fornece controle total da classe que a exibição herda:


CSHTML

@inherits TypeNameOfClassToInheritFrom

O código a seguir é um tipo de página personalizado Razor :

C#

using Microsoft.AspNetCore.Mvc.Razor;

public abstract class CustomRazorPage<TModel> : RazorPage<TModel>


{
public string CustomText { get; } =
"Gardyloo! - A Scottish warning yelled from a window before dumping"
+
"a slop bucket on the street below.";
}

O CustomText é exibido em uma exibição:

CSHTML

@inherits CustomRazorPage<TModel>

<div>Custom text: @CustomText</div>

O código renderiza o HTML a seguir:

HTML

<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 _ViewImports.cshtml arquivo que o modo de exibição importa:

CSHTML

@inherits CustomRazorPage<TModel>

O código a seguir é um exemplo de exibição fortemente tipada:

CSHTML
@inherits CustomRazorPage<TModel>

<div>The Login Email: @Model.Email</div>


<div>Custom text: @CustomText</div>

Se "rick@contoso.com" for passado no modelo, a exibição gerará a seguinte marcação


HTML:

HTML

<div>The Login Email: rick@contoso.com</div>


<div>
Custom text: Gardyloo! - A Scottish warning yelled from a window before
dumping
a slop bucket on the street below.
</div>

@inject

A @inject diretiva permite que a Razor Página injete um serviço do contêiner de serviço
em um modo de exibição. Para obter mais informações, consulte Injeção de
dependência em exibições.

@layout

Esse cenário só se aplica a Razor componentes ( .razor ).

A @layout diretiva especifica um layout para componentes roteáveis Razor que têm
uma @page diretiva. Os componentes de layout são usados para evitar casos de
duplicação e inconsistência no código. Para obter mais informações, consulte ASP.NET
Core Blazor layouts.

@model

Esse cenário só se aplica a exibições e Razor páginas do MVC ( .cshtml ).

A diretiva @model especifica o tipo do modelo passado para uma exibição ou página:

CSHTML

@model TypeNameOfModel
Em um ASP.NET Core aplicativo MVC ou Razor Pages criado com contas de usuário
individuais, Views/Account/Login.cshtml contém a seguinte declaração de modelo:

CSHTML

@model LoginViewModel

A classe gerada herda de RazorPage<LoginViewModel> :

C#

public class _Views_Account_Login_cshtml : RazorPage<LoginViewModel>

Razor expõe uma Model propriedade para acessar o modelo passado para o modo de
exibição:

CSHTML

<div>The Login Email: @Model.Email</div>

A diretiva @model especifica o tipo da propriedade Model . 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 . Para obter mais informações,
consulte Strongly typed models and the @model keyword.

@namespace

A diretiva @namespace :

Define o namespace da classe da página gerada Razor , exibição MVC ou Razor


componente.
Define os namespaces derivados raiz de uma página, exibições ou classes de
componentes do arquivo de importações mais próximo na árvore de diretório,
_ViewImports.cshtml (exibições ou páginas) ou _Imports.razor (Razor
componentes).

CSHTML

@namespace Your.Namespace.Here

Para o Razor exemplo de Páginas mostrado na tabela a seguir:


Cada página importa Pages/_ViewImports.cshtml .
Pages/_ViewImports.cshtml contém @namespace Hello.World .
Cada página tem Hello.World como a raiz do namespace.

? Namespace

Pages/Index.cshtml Hello.World

Pages/MorePages/Page.cshtml Hello.World.MorePages

Pages/MorePages/EvenMorePages/Page.cshtml Hello.World.MorePages.EvenMorePages

As relações anteriores se aplicam à importação de arquivos usados com exibições e


Razor componentes do MVC.

Quando vários arquivos de importação têm uma diretiva @namespace , o arquivo mais
próximo da página, exibição ou componente na árvore de diretórios é usado para
definir o namespace raiz.

Se a EvenMorePages pasta no exemplo anterior tiver um arquivo de importações com


@namespace Another.Planet (ou o Pages/MorePages/EvenMorePages/Page.cshtml arquivo

contiver @namespace Another.Planet ), o resultado será mostrado na tabela a seguir.

? Namespace

Pages/Index.cshtml Hello.World

Pages/MorePages/Page.cshtml Hello.World.MorePages

Pages/MorePages/EvenMorePages/Page.cshtml Another.Planet

@page

A diretiva @page tem efeitos diferentes dependendo do tipo do arquivo em que


aparece. A diretiva:

Em um .cshtml arquivo, indica que o arquivo é uma Razor página. Para obter mais
informações, consulte Rotas personalizadas e introdução às Razor páginas no
ASP.NET Core.
Especifica que um Razor componente deve lidar diretamente com solicitações.
Para obter mais informações, consulte ASP.NET Core Blazor roteamento e
navegação.
@preservewhitespace

Esse cenário só se aplica a Razor componentes ( .razor ).

Quando definido como false (padrão), o espaço em branco na marcação renderizada


dos Razor componentes ( .razor ) será removido se:

Estiver à esquerda ou à direita dentro de um elemento.


À esquerda ou à direita dentro de um RenderFragment parâmetro. Por exemplo, o
conteúdo filho passado para outro componente.
Preceder ou seguir um bloco de código C#, como @if ou @foreach .

@section

Esse cenário só se aplica a exibições e Razor páginas do MVC ( .cshtml ).

A @section diretiva é usada em conjunto com layouts de MVC e Razor Páginas para
permitir que exibições ou páginas renderizem conteúdo em diferentes partes da página
HTML. Saiba mais em Layout no ASP.NET Core.

@using

A diretiva @using adiciona a diretiva using de C# à exibição gerada:

CSHTML

@using System.IO
@{
var dir = Directory.GetCurrentDirectory();
}
<p>@dir</p>

Nos Razor componentes, @using também controla quais componentes estão no escopo.

Atributos de diretiva
Razor os atributos de diretiva são representados por expressões implícitas com
palavras-chave reservadas após o @ símbolo. Normalmente, um atributo de diretiva
altera a forma como um elemento é analisado ou habilita uma funcionalidade diferente.

@attributes
Esse cenário só se aplica a Razor componentes ( .razor ).

@attributes permite que um componente renderize atributos não declarados. Para


obter mais informações, consulte ASP.NET Core Razor componentes.

@bind

Esse cenário só se aplica a Razor componentes ( .razor ).

A vinculação de dados nos componentes é realizada com o atributo @bind . Para obter
mais informações, consulte ASP.NET Core Blazor associação de dados.

@bind:culture

Esse cenário só se aplica a Razor componentes ( .razor ).

Use o @bind:culture atributo com o @bind atributo para fornecer um


System.Globalization.CultureInfo valor para análise e formatação de um valor. Para obter
mais informações, consulte ASP.NET Core Blazor globalização e localização.

@on{EVENT}

Esse cenário só se aplica a Razor componentes ( .razor ).

Razor fornece recursos de tratamento de eventos para componentes. Para obter mais
informações, consulte ASP.NET Core Blazor tratamento de eventos.

@on{EVENT}:preventDefault

Esse cenário só se aplica a Razor componentes ( .razor ).

Impede a ação padrão do evento.

@on{EVENT}:stopPropagation

Esse cenário só se aplica a Razor componentes ( .razor ).

Interrompe a propagação de eventos para o evento.

@key
Esse cenário só se aplica a Razor componentes ( .razor ).

O atributo da diretiva @key faz com que os componentes comparem o algoritmo para
garantir a preservação de elementos ou componentes com base no valor da chave. Para
obter mais informações, consulte ASP.NET Core Razor componentes.

@ref

Esse cenário só se aplica a Razor componentes ( .razor ).

Referências de componente ( @ref ) proporcionam uma maneira de fazer referência a


uma instância de componente para que você possa emitir comandos para essa
instância. Para obter mais informações, consulte ASP.NET Core Razor componentes.

@typeparam

Esse cenário só se aplica a Razor componentes ( .razor ).

A diretiva @typeparam declara um parâmetro de tipo genérico para a classe de


componente gerada:

razor

@typeparam TEntity

Há suporte para tipos genéricos com where restrições de tipo:

razor

@typeparam TEntity where TEntity : IEntity

Para obter mais informações, confira os seguintes artigos:

Razor componentes ASP.NET Core


Componentes com modelo Blazor no ASP.NET Core

Delegados de modelo Razor


Razor os modelos permitem que você defina um snippet de interface do usuário com o
seguinte formato:

CSHTML
@<tag>...</tag>

O exemplo a seguir ilustra como especificar um delegado modelo Razor 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 .

C#

public class Pet


{
public string Name { get; set; }
}

CSHTML

@{
Func<dynamic, object> petTemplate = @<p>You have a pet named
<strong>@item.Name</strong>.</p>;

var pets = new List<Pet>


{
new Pet { Name = "Rin Tin Tin" },
new Pet { Name = "Mr. Bigglesworth" },
new Pet { Name = "K-9" }
};
}

O modelo é renderizado com pets fornecido por uma instrução foreach :

CSHTML

@foreach (var pet in pets)


{
@petTemplate(pet)
}

Saída renderizada:

HTML

<p>You have a pet named <strong>Rin Tin Tin</strong>.</p>


<p>You have a pet named <strong>Mr. Bigglesworth</strong>.</p>
<p>You have a pet named <strong>K-9</strong>.</p>
Você também pode fornecer um modelo embutido Razor como um argumento para um
método. No exemplo a seguir, o Repeat método recebe um Razor modelo. O método
usa o modelo para produzir o conteúdo HTML com repetições de itens fornecidos em
uma lista:

CSHTML

@using Microsoft.AspNetCore.Html

@functions {
public static IHtmlContent Repeat(IEnumerable<dynamic> items, int times,
Func<dynamic, IHtmlContent> template)
{
var html = new HtmlContentBuilder();

foreach (var item in items)


{
for (var i = 0; i < times; i++)
{
html.AppendHtml(template(item));
}
}

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.

CSHTML

<ul>
@Repeat(pets, 3, @<li>@item.Name</li>)
</ul>

Saída renderizada:

HTML

<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
Esse cenário só se aplica a exibições de MVC e Razor páginas ( .cshtml ).

Há três diretivas que relacionadas aos Auxiliares de marca.

Diretiva Função

@addTagHelper Disponibiliza os Auxiliares de marca para uma exibição.

@removeTagHelper Remove os Auxiliares de marca adicionados anteriormente de uma


exibição.

@tagHelperPrefix Especifica um prefixo de marca para habilitar o suporte do Auxiliar de


marca e tornar explícito o uso do Auxiliar de marca.

Razor palavras-chave reservadas

Razor Keywords
page

namespace
functions

inherits
model

section

helper (Atualmente não há suporte para ASP.NET Core)

Razor as palavras-chave são escapadas com @(Razor Keyword) (por exemplo,


@(functions) ).

Palavras-chave C# Razor
case

do
default

for
foreach

if
else

lock

switch
try

catch
finally

using

while

As palavras-chave C# Razor devem ser escapadas duas vezes ( @(@C# Razor Keyword)
por exemplo, @(@case) ). O primeiro @ escapa do Razor analisador. O segundo @ faz o
escape do analisador C#.

Palavras-chave reservadas não usadas por Razor


class

Inspecionar a Razor classe C# gerada para


obter uma exibição
O Razor SDK manipula a compilação de Razor arquivos. Por padrão, os arquivos de
código gerados não são emitidos. Para habilitar a emissão dos arquivos de código,
defina a EmitCompilerGeneratedFiles diretiva no arquivo de projeto ( .csproj ) como
true :

XML

<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

Ao criar um projeto 6.0 ( net6.0 ) na configuração de Debug build, o Razor SDK gera um
obj/Debug/net6.0/generated/ diretório na raiz do projeto. Seu subdiretório contém os

arquivos de código de página emitidos Razor .


Pesquisas de exibição e diferenciação de
maiúsculas e minúsculas
O Razor mecanismo de exibição executa pesquisas que diferenciam maiúsculas de
minúsculas para exibições. No entanto, a pesquisa real é determinada pelo sistema de
arquivos subjacente:

Origem baseada em arquivo:


Em sistemas operacionais com sistemas de arquivos que não diferenciam
maiúsculas e minúsculas (por exemplo, Windows), pesquisas no provedor de
arquivos físico não diferenciam maiúsculas de minúsculas. Por exemplo, return
View("Test") resulta em correspondências para /Views/Home/Test.cshtml ,

/Views/home/test.cshtml e qualquer outra variante de casing.


Em sistemas de arquivos que diferenciam maiúsculas de minúsculas (por
exemplo, Linux, OSX e com EmbeddedFileProvider ), as pesquisas diferenciam
maiúsculas de minúsculas. Por exemplo, return View("Test") corresponde
/Views/Home/Test.cshtml especificamente a .

Exibições pré-compiladas: com o ASP.NET Core 2.0 e posteriores, pesquisar em


exibições pré-compiladas não diferencia maiúsculas de minúsculas em nenhum
sistema operacional. O comportamento é idêntico ao comportamento do provedor
de arquivos físico no Windows. Se duas exibições pré-compiladas diferirem apenas
quanto ao padrão de maiúsculas e minúsculas, o resultado da pesquisa não será
determinístico.

Os desenvolvedores são incentivados a fazer a correspondência entre as maiúsculas e


minúsculas dos nomes dos arquivos e de diretórios com o uso de maiúsculas e
minúsculas em:

Nomes de área, controlador e ação.


Razor Páginas.

Fazer essa correspondência garante que as implantações encontrem suas exibições,


independentemente do sistema de arquivos subjacente.

Importações usadas por Razor


As seguintes importações são geradas pelos modelos web ASP.NET Core para dar
suporte Razor a Arquivos:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

Recursos adicionais
Introdução à programação da Web ASP.NET usando o Razor A sintaxe fornece muitos
exemplos de programação com Razor sintaxe.
Criar interface do usuário reutilizável
usando o Razor projeto de biblioteca de
classes no ASP.NET Core
Artigo • 28/11/2022 • 25 minutos para o fim da leitura

De Rick Anderson

Razor exibições, páginas, controladores, modelos de página, Razor componentes,


Componentes de exibição e modelos de dados podem ser incorporados a uma Razor
RCL (biblioteca de classes). A RCL pode ser e empacotada e reutilizada. Os aplicativos
podem incluir a RCL e substituir as exibições e as páginas que ela contém. Quando um
modo de exibição, exibição parcial ou Razor Página é encontrado no aplicativo Web e
na RCL, a Razor marcação ( .cshtml arquivo) no aplicativo Web tem precedência.

Para obter informações sobre como integrar o npm e o webpack ao processo de build
de uma Razor Biblioteca de Classes, consulte Criar ativos web cliente para sua Razor
Biblioteca de Classes .

Criar uma biblioteca de classes que contém a


Razor interface do usuário
Visual Studio

No Visual Studio, selecione Criar um novo projeto.


Selecione Razor Biblioteca de Classes>Avançar.
Nomeie a biblioteca (por exemplo, "RazorClassLib"), >Criar. Para evitar uma
colisão de nome de arquivo com a biblioteca de exibição gerada, verifique se
o nome da biblioteca não termina em .Views .
Selecione Páginas e exibições de suporte se precisar dar suporte a exibições.
Por padrão, há suporte apenas Razor para Páginas. Selecione Criar.

O Razor modelo RCL (biblioteca de classes) usa como padrão o Razor


desenvolvimento de componentes por padrão. A opção Páginas e exibições de
suporte dá suporte a páginas e exibições.

Adicione Razor arquivos à RCL.


Os modelos de ASP.NET Core pressupõem que o conteúdo RCL esteja na Areas pasta .
Veja o layout de Páginas RCL abaixo para criar uma RCL que expõe o conteúdo em
~/Pages vez de ~/Areas/Pages .

Referenciar conteúdo RCL


A RCL pode ser referenciada por:

Pacote do NuGet. Confira Criando pacotes do NuGet, dotnet add package e Criar e
publicar um pacote do NuGet.
{ProjectName}.csproj . Confira dotnet-add reference.

Substituir exibições, exibições parciais e


páginas
Quando um modo de exibição, exibição parcial ou Razor Página é encontrado no
aplicativo Web e na RCL, a Razor marcação ( .cshtml arquivo) no aplicativo Web tem
precedência. Por exemplo, adicionar WebApp1/Areas/MyFeature/Pages/Page1.cshtml ao
WebApp1 e Page1 no WebApp1 terá precedência sobre Page1 na RCL.

No download de exemplo, renomeie WebApp1/Areas/MyFeature2 para


WebApp1/Areas/MyFeature para precedência de teste.

Copie a exibição RazorUIClassLib/Areas/MyFeature/Pages/Shared/_Message.cshtml


parcial para WebApp1/Areas/MyFeature/Pages/Shared/_Message.cshtml . Atualize a
marcação para indicar o novo local. Crie e execute o aplicativo para verificar se a versão
da parcial do aplicativo está sendo usada.

Se a RCL usar Páginas, habilite os Razor serviços Razor de Páginas e os pontos de


extremidade no aplicativo de hospedagem:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.MapRazorPages();
app.Run();

Layout de Páginas RCL


Para fazer referência ao conteúdo RCL como se ele fosse parte da pasta do Pages
aplicativo Web, crie o projeto RCL com a seguinte estrutura de arquivos:

RazorUIClassLib/Pages
RazorUIClassLib/Pages/Shared

Suponha que contenha RazorUIClassLib/Pages/Shared dois arquivos parciais:


_Header.cshtml e _Footer.cshtml . As <partial> marcas podem ser adicionadas ao
_Layout.cshtml arquivo:

CSHTML

<body>
<partial name="_Header">
@RenderBody()
<partial name="_Footer">
</body>

Adicione o _ViewStart.cshtml arquivo à pasta do Pages projeto RCL para usar o


_Layout.cshtml arquivo do aplicativo Web host:

CSHTML

@{
Layout = "_Layout";
}
Criar uma RCL com ativos estáticos
Uma RCL pode exigir ativos estáticos complementares que podem ser referenciados
pela RCL ou pelo aplicativo de consumo da RCL. ASP.NET Core permite a criação de
RCLs que incluem ativos estáticos disponíveis para um aplicativo de consumo.

Para incluir ativos complementares como parte de uma RCL, crie uma wwwroot pasta na
biblioteca de classes e inclua todos os arquivos necessários nessa pasta.

Ao empacotar uma RCL, todos os ativos complementares na wwwroot pasta são


incluídos automaticamente no pacote.

Use o dotnet pack comando em vez da versão nuget pack NuGet.exe .

Excluir ativos estáticos


Para excluir ativos estáticos, adicione o caminho de exclusão desejado ao
$(DefaultItemExcludes) grupo de propriedades no arquivo de projeto. Separar entradas
com ponto e vírgula ( ; ).

No exemplo a seguir, a lib.css folha de estilos na wwwroot pasta não é considerada um


ativo estático e não está incluída na RCL publicada:

XML

<PropertyGroup>

<DefaultItemExcludes>$(DefaultItemExcludes);wwwroot\lib.css</DefaultItemExcl
udes>
</PropertyGroup>

Integração do Typescript
Para incluir arquivos TypeScript em uma RCL:

1. Referencie o Microsoft.TypeScript.MSBuild pacote NuGet no projeto.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET,


consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de
consumo de pacotes (documentação do NuGet). Confirme as versões
corretas de pacote em NuGet.org .
2. Coloque os arquivos TypeScript ( .ts ) fora da wwwroot pasta. Por exemplo, coloque
os arquivos em uma Client pasta.

3. Configure a saída de build do TypeScript para a wwwroot pasta. Defina a


TypescriptOutDir propriedade dentro de um PropertyGroup no arquivo de
projeto:

XML

<TypescriptOutDir>wwwroot</TypescriptOutDir>

4. Inclua o destino TypeScript como uma dependência do destino adicionando


PrepareForBuildDependsOn o seguinte destino dentro de um PropertyGroup no
arquivo de projeto:

XML

<PrepareForBuildDependsOn>
CompileTypeScript;
GetTypeScriptOutputForPublishing;$(PrepareForBuildDependsOn)
</PrepareForBuildDependsOn>

Consumir conteúdo de uma RCL referenciada


Os arquivos incluídos na wwwroot pasta da RCL são expostos à RCL ou ao aplicativo de
consumo no prefixo _content/{PACKAGE ID}/ . Por exemplo, uma biblioteca com um
nome de assembly de Razor.Class.Lib e sem um <PackageId> especificado em seu
arquivo de projeto resulta em um caminho para o conteúdo estático em
_content/Razor.Class.Lib/ . Ao produzir um pacote NuGet e o nome do assembly não é

o mesmo que a ID do pacote (<PackageId> no arquivo de projeto da biblioteca), use a


ID do pacote conforme especificado no arquivo de projeto para {PACKAGE ID} .

O aplicativo de consumo faz referência a ativos estáticos fornecidos pela biblioteca com
<script> , <style> , <img> e outras marcas HTML. O aplicativo de consumo deve ter

suporte a arquivos estáticos habilitados em:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.MapRazorPages();
app.Run();

Ao executar o aplicativo de consumo da saída de build ( dotnet run ), os ativos da Web


estáticos são habilitados por padrão no ambiente de desenvolvimento. Para dar suporte
a ativos em outros ambientes ao executar a partir da saída de build, chame
UseStaticWebAssets no construtor de host em Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseWebRoot("wwwroot");
builder.WebHost.UseStaticWebAssets();

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();
app.MapRazorPages();

app.Run();

A chamada UseStaticWebAssets não é necessária ao executar um aplicativo da saída


publicada ( dotnet publish ).

Fluxo de desenvolvimento de vários projetos


Quando o aplicativo de consumo é executado:

Os ativos na RCL permanecem em suas pastas originais. Os ativos não são movidos
para o aplicativo de consumo.
Qualquer alteração dentro da pasta da wwwroot RCL é refletida no aplicativo de
consumo depois que a RCL é reconstruída e sem recriar o aplicativo de consumo.

Quando a RCL é criada, um manifesto é produzido que descreve os locais de ativos da


Web estáticos. O aplicativo de consumo lê o manifesto em runtime para consumir os
ativos de projetos e pacotes referenciados. Quando um novo ativo é adicionado a uma
RCL, a RCL deve ser recriada para atualizar seu manifesto antes que um aplicativo
consumidor possa acessar o novo ativo.

Publicar
Quando o aplicativo é publicado, os ativos complementares de todos os projetos e
pacotes referenciados são copiados para a wwwroot pasta do aplicativo
_content/{PACKAGE ID}/ publicado em . Ao produzir um pacote NuGet e o nome do

assembly não é o mesmo que a ID do pacote (<PackageId> no arquivo de projeto da


biblioteca), use a ID do pacote conforme especificado no arquivo de projeto para
{PACKAGE ID} ao examinar a wwwroot pasta dos ativos publicados.

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

Consumir componentes do Razor de uma Razor biblioteca de classes (RCL)

Blazor ASP.NET Core isolamento de CSS


Auxiliares de marcação internos do
ASP.NET Core
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Por Peter Kellner

Para ter uma visão geral dos Auxiliares de Marcação, confira Auxiliares de Marcação no
ASP.NET Core.

Há auxiliares de marcação internos que não estão listados neste documento. Os


Auxiliares de Marcação não listados são usados internamente pelo mecanismo de
exibição Razor. O auxiliar de marcação do caractere ~ (til) não está listado. O auxiliar de
marcação de til é expandido para o caminho raiz do site.

Auxiliares de marcação internos do ASP.NET


Core
Auxiliar de marcação de âncora

Auxiliar de Marcação de cache

Auxiliar de Marcação de componente

Auxiliar de Marcação de cache distribuído

Auxiliar de marcação de ambiente

Auxiliar de Marcação de formulário

Auxiliar de marcação de ação de formulário

Auxiliar de marcação de imagem

Auxiliar de marcação de entrada

Auxiliar de marcação de rótulo

Auxiliar de Marcação de link

Auxiliar de marca parcial

Auxiliar de Marcação de script

Auxiliar de Marcação de seleção


Auxiliar de Marcação de Textarea

Auxiliar de marcação de mensagem de validação

Resumo de validação de auxiliar de marcação

Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Componentes do Auxiliar de Marca no ASP.NET Core
Auxiliares de Marca no ASP.NET Core
Artigo • 28/11/2022 • 14 minutos para o fim da leitura

De Rick Anderson

O que são Auxiliares de Marca


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. Por exemplo, o ImageTagHelper
interno pode acrescentar um número de versão ao nome da imagem. Sempre que a
imagem é alterada, o servidor gera uma nova versão exclusiva para a imagem, de modo
que os clientes tenham a garantia de obter a imagem atual (em vez de uma imagem
obsoleta armazenada em cache). Há muitos Auxiliares de Marca internos para tarefas
comuns – como criação de formulários, links, carregamento de ativos e muito mais – e
ainda outros disponíveis em repositórios GitHub públicos e como NuGet. Os Auxiliares
de Marca são criados no C# e são direcionados a elementos HTML de acordo com o
nome do elemento, o nome do atributo ou a marca pai. Por exemplo, o LabelTagHelper
interno pode ser direcionado ao elemento <label> HTML quando os atributos
LabelTagHelper são aplicados. Se você estiver familiarizado com auxiliares HTML, os

Auxiliares de Marca reduzirão as transições explícitas entre HTML e C# nos Razor


modos de exibição. Em muitos casos, os Auxiliares HTML fornecem uma abordagem
alternativa a um Auxiliar de Marca específico, mas é importante reconhecer que os
Auxiliares de Marca não substituem os Auxiliares HTML e que não há um Auxiliar de
Marca para cada Auxiliar HTML. Comparação entre Auxiliares de Marca e Auxiliares
HTML explica as diferenças mais detalhadamente.

Não há suporte para auxiliares de marca em Razor componentes. Para obter mais
informações, consulte ASP.NET Core Razor componentes.

O que os Auxiliares de Marca fornecem


Uma experiência de desenvolvimento amigável a HTML

Na maioria das vezes, Razor a marcação usando auxiliares de marca parece html padrão.
Designers de front-end conversando com HTML/CSS/JavaScript podem editar Razor
sem aprender a sintaxe C# Razor .

Um ambiente intelliSense avançado para criar HTML e Razor marcação


Isso contrasta fortemente com auxiliares HTML, a abordagem anterior para a criação de
marcação no lado do servidor em Razor exibições. Comparação entre Auxiliares de
Marca e Auxiliares HTML explica as diferenças mais detalhadamente. Suporte do
IntelliSense para Auxiliares de Marca explica o ambiente do IntelliSense. Até mesmo os
desenvolvedores com Razor sintaxe C# são mais produtivos usando auxiliares de
marcação do que gravando marcação C# Razor .

Uma maneira de tornar você mais produtivo e capaz de produzir um código mais
robusto, confiável e mantenedível usando informações disponíveis apenas no
servidor

Por exemplo, historicamente, o mantra sobre a atualização de imagens era alterar o


nome da imagem quando você alterava a imagem. As imagens devem ser armazenadas
em cache de forma agressiva por motivos de desempenho e, a menos que você altere o
nome de uma imagem, você corre o risco de os clientes obterem uma cópia obsoleta.
Historicamente, depois que uma imagem era editada, o nome precisava ser alterado e
cada referência à imagem no aplicativo Web precisava ser atualizada. Não só é muito
trabalhoso, como também é propenso a erros (você pode perder uma referência, inserir
acidentalmente a cadeia de caracteres errada, etc.) O interno ImageTagHelper pode fazer
isso para você automaticamente. O ImageTagHelper pode acrescentar um número de
versão ao nome da imagem, de modo que sempre que a imagem é alterada, o servidor
gera automaticamente uma nova versão exclusiva para a imagem. Os clientes têm a
garantia de obter a imagem atual. Basicamente, essa economia na robustez e no
trabalho é obtida gratuitamente com o ImageTagHelper .

A maioria dos auxiliares de marca internos é direcionada a elementos HTML padrão e


fornece atributos do lado do servidor para o elemento. Por exemplo, o elemento
<input> usado em várias exibições na pasta Exibição/Conta contém o atributo asp-for .

Esse atributo extrai o nome da propriedade do modelo especificado no HTML


renderizado. Considere uma Razor exibição com o seguinte modelo:

C#

public class Movie


{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}

A seguinte Razor marcação:


CSHTML

<label asp-for="Movie.Title"></label>

Gera o seguinte HTML:

HTML

<label for="Movie_Title">Title</label>

O atributo asp-for é disponibilizado pela propriedade For no LabelTagHelper. Confira


Auxiliares de marca de autor para obter mais informações.

Gerenciando o escopo do Auxiliar de Marca


O escopo dos Auxiliares de Marca é controlado por uma combinação de @addTagHelper ,
@removeTagHelper e o caractere de recusa "!".

@addTagHelper disponibiliza os Auxiliares de Marca

Se você criar um novo aplicativo Web ASP.NET Core chamado AuthoringTagHelpers, o


seguinte Views/_ViewImports.cshtml arquivo será adicionado ao seu projeto:

CSHTML

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers

A diretiva @addTagHelper disponibiliza os Auxiliares de Marca para a exibição. Nesse


caso, o arquivo de exibição é Pages/_ViewImports.cshtml , que por padrão é herdado por
todos os arquivos na pasta Páginas e subpastas; disponibilizando auxiliares de marca. O
código acima usa a sintaxe curinga ("*") para especificar que todos os Auxiliares de
Marca no assembly especificado (Microsoft.AspNetCore.Mvc.TagHelpers) estarão
disponíveis para todos os arquivos de exibição no diretório views ou subdiretório. O
primeiro parâmetro depois @addTagHelper especifica os Auxiliares de Marca a serem
carregados (estamos usando "*" para todos os Auxiliares de Marca) e o segundo
parâmetro "Microsoft.AspNetCore.Mvc.TagHelpers" especifica o assembly que contém
os Auxiliares de Marca. Microsoft.AspNetCore.Mvc.TagHelpers é o assembly para os
Auxiliares de Marca internos do ASP.NET Core.
Para expor todos os Auxiliares de Marca neste projeto (que cria um assembly chamado
AuthoringTagHelpers), você usará o seguinte:

CSHTML

@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers

Se o projeto contém um EmailTagHelper com o namespace padrão


( AuthoringTagHelpers.TagHelpers.EmailTagHelper ), forneça o FQN ( nome totalmente
qualificado) do Auxiliar de Marca:

CSHTML

@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
curinga "*". A sintaxe curinga permite inserir o caractere curinga "*" como o sufixo em
um FQN. Por exemplo, uma das seguintes diretivas exibirá o EmailTagHelper :

CSHTML

@addTagHelper AuthoringTagHelpers.TagHelpers.E*, AuthoringTagHelpers


@addTagHelper AuthoringTagHelpers.TagHelpers.Email*, AuthoringTagHelpers

Como mencionado anteriormente, adicionar a @addTagHelper diretiva ao


Views/_ViewImports.cshtml arquivo disponibiliza o Auxiliar de Marca para todos os

arquivos de exibição no diretório de Exibições e subdiretórios. Use a diretiva


@addTagHelper nos arquivos de exibição específicos se você deseja aceitar a exposição
do Auxiliar de Marca a apenas essas exibições.

@removeTagHelper remove os Auxiliares de Marca

O @removeTagHelper tem os mesmos dois parâmetros @addTagHelper e remove um


Auxiliar de Marca adicionado anteriormente. Por exemplo, @removeTagHelper aplicado a
uma exibição específica remove o Auxiliar de Marca especificado da exibição. Usar
@removeTagHelper em um Views/Folder/_ViewImports.cshtml arquivo remove o Auxiliar

de Marca especificado de todos os modos de exibição na Pasta.

Controlando o escopo do Auxiliar de Marca com o


_ViewImports.cshtml arquivo

Você pode adicionar uma _ViewImports.cshtml a qualquer pasta de exibição e o


mecanismo de exibição aplica as diretivas desse arquivo e do
Views/_ViewImports.cshtml arquivo. Se você adicionou um arquivo vazio

Views/Home/_ViewImports.cshtml para os Home modos de exibição, não haverá alteração

porque o _ViewImports.cshtml arquivo é aditivo. Todas @addTagHelper as diretivas


adicionadas ao Views/Home/_ViewImports.cshtml arquivo (que não estão no arquivo
padrão Views/_ViewImports.cshtml ) exporiam esses Auxiliares de Marca a exibições
somente na Home pasta.

Recusando elementos individuais


Desabilite um Auxiliar de Marca no nível do elemento com o caractere de recusa do
Auxiliar de Marca ("!"). Por exemplo, a validação Email está desabilitada no <span> com
o caractere de recusa do Auxiliar de Marca:

CSHTML

<!span asp-validation-for="Email" class="text-danger"></!span>

É necessário aplicar o caractere de recusa do Auxiliar de Marca à marca de abertura e


fechamento. (O editor do Visual Studio adiciona automaticamente o caractere de recusa
à marca de fechamento quando um é adicionado à marca de abertura). Depois de
adicionar o caractere de recusa, o elemento e os atributos do Auxiliar de Marca deixam
de ser exibidos em uma fonte diferenciada.

Usando @tagHelperPrefix para tornar explícito o uso do


Auxiliar de Marca
A diretiva @tagHelperPrefix permite que você especifique uma cadeia de caracteres de
prefixo de marca para habilitar o suporte do Auxiliar de Marca e tornar explícito o uso
do Auxiliar de Marca. Por exemplo, você pode adicionar a seguinte marcação ao
Views/_ViewImports.cshtml arquivo:

CSHTML
@tagHelperPrefix th:

Na imagem do código abaixo, o prefixo do Auxiliar de Marca é definido como th: ;


portanto, somente esses elementos que usam o prefixo th: dão suporte a Auxiliares de
Marca (elementos habilitados para Auxiliar de Marca têm uma fonte diferenciada). Os
elementos <label> e <input> têm o prefixo do Auxiliar de Marca e são habilitados para
Auxiliar de Marca, ao contrário do elemento <span> .

As mesmas regras de hierarquia que se aplicam a @addTagHelper também se aplicam a


@tagHelperPrefix .

Auxiliares de Marca com autofechamento


Muitos Auxiliares de Marca não podem ser usados como marcações com
autofechamento. Alguns Auxiliares de Marca são projetados para serem marcações com
autofechamento. Usar um Auxiliar de Marca que não foi projetado para ser de
autofechamento suprime a saída renderizada. Um Auxiliar de Marca com
autofechamento resulta em uma marca com autofechamento na saída renderizada. Para
obter mais informações, confira esta observação em Criando Auxiliares de Marca.

C# no atributo/declaração de Auxiliares de
Marca
Os Auxiliares de Marca não permitem C# na área de declaração de atributo ou marca do
elemento. Por exemplo, o código a seguir não é válido:

CSHTML

<input asp-for="LastName"
@(Model?.LicenseId == null ? "disabled" : string.Empty) />

O código anterior pode ser escrito como:

CSHTML
<input asp-for="LastName"
disabled="@(Model?.LicenseId == null)" />

Normalmente, o @ operador insere uma representação textual de uma expressão na


marcação HTML renderizada. No entanto, quando uma expressão é avaliada como
lógica false , a estrutura remove o atributo. No exemplo anterior, o disabled atributo é
definido como true se for Model ou LicenseId não null .

Inicializadores auxiliares de marca


Embora os atributos possam ser usados para configurar instâncias individuais de
auxiliares de marca, ITagHelperInitializer<TTagHelper> podem ser usados para
configurar todas as instâncias auxiliares de marca de um tipo específico. Considere o
exemplo a seguir de um inicializador auxiliar de marca que configura o asp-append-
version atributo ou AppendVersion a propriedade para todas as instâncias do
ScriptTagHelper aplicativo:

C#

public class AppendVersionTagHelperInitializer :


ITagHelperInitializer<ScriptTagHelper>
{
public void Initialize(ScriptTagHelper helper, ViewContext context)
{
helper.AppendVersion = true;
}
}

Para usar o inicializador, configure-o registrando-o como parte da inicialização do


aplicativo:

C#

builder.Services.AddSingleton
<ITagHelperInitializer<ScriptTagHelper>,
AppendVersionTagHelperInitializer>();

Tag Helper automatic version generation


outside of wwwroot
Para que um Auxiliar de Marca gere uma versão para um arquivo estático fora wwwroot ,
consulte Arquivos do Serve de vários locais
Suporte do IntelliSense para Auxiliares de
Marca
Considere a escrita de um elemento <label> HTML. Assim que você insere <l no editor
do Visual Studio, o IntelliSense exibe elementos correspondentes:

Você não só obtém ajuda HTML, mas também o ícone (o símbolo "@" com "<>" sob
ele).

O ícone identifica o elemento como direcionado pelos Auxiliares de Marca. Elementos


HTML puros (como o fieldset ) exibem o ícone "<>".

Uma marca <label> HTML pura exibe a marca HTML (com o tema de cores padrão do
Visual Studio) em uma fonte marrom, os atributos em vermelho e os valores de atributo
em azul.

Depois de inserir <label , o IntelliSense lista os atributos HTML/CSS disponíveis e os


atributos direcionados ao Auxiliar de Marca:
O preenchimento de declaração do IntelliSense permite que você pressione a tecla TAB
para preencher a declaração com o valor selecionado:

Assim que um atributo do Auxiliar de Marca é inserido, as fontes da marca e do atributo


são alteradas. Usando o tema de cores padrão "Azul" ou "Claro" do Visual Studio, a
fonte é roxo em negrito. Se estiver usando o tema "Escuro", a fonte será azul-petróleo
em negrito. As imagens deste documento foram obtidas usando o tema padrão.

Você pode inserir o atalho Do Visual Studio CompleteWord (Ctrl +barra de espaços é o
padrão) dentro das aspas duplas (""), e agora você está em C#, assim como estaria em
uma classe C#. O IntelliSense exibe todos os métodos e propriedades no modelo de
página. Os métodos e as propriedades estão disponíveis porque o tipo de propriedade
é ModelExpression . Na imagem abaixo, estou editando a exibição Register e, portanto,
o RegisterViewModel está disponível.

O IntelliSense lista as propriedades e os métodos disponíveis para o modelo na página.


O ambiente avançado de IntelliSense ajuda você a selecionar a classe CSS:
Comparação entre Auxiliares de Marca e
Auxiliares HTML
Auxiliares de marca anexam a elementos HTML em Razor exibições, enquanto auxiliares
HTML são invocados como métodos intercalados com HTML em Razor exibições.
Considere a seguinte Razor marcação, que cria um rótulo HTML com a classe CSS
"caption":

CSHTML

@Html.Label("FirstName", "First Name:", new {@class="caption"})

O símbolo at ( @ ) informa Razor 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:

CSHTML

new {@class="caption"}

É um objeto anônimo usado para representar atributos. Como a class é 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 C# e Razor), a maior parte da linha é estrangeira. Toda a linha precisa
ser criada sem nenhuma ajuda do IntelliSense.
Usando o LabelTagHelper , a mesma marcação pode ser escrita como:

CSHTML

<label class="caption" asp-for="FirstName"></label>

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.

A imagem de código a seguir mostra a parte formulário do modo de


Views/Account/Register.cshtml Razor exibição gerado a partir do modelo 4.5.x MVC de
ASP.NET incluído no Visual Studio.
O editor do Visual Studio exibe o código C# com uma tela de fundo cinza. Por exemplo,
o Auxiliar HTML AntiForgeryToken :

CSHTML

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

CSHTML

<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>

Cada um dos atributos "asp-" tem um valor "Email", mas "Email" não é uma cadeia de
caracteres. Nesse contexto, "Email" é a propriedade da expressão do modelo C# para o
RegisterViewModel .

O editor do Visual Studio ajuda você a escrever toda a marcação na abordagem do


Auxiliar de Marca de formulário de registro, enquanto o Visual Studio não fornece
nenhuma ajuda para a maioria do código na abordagem de Auxiliares HTML. Suporte
do IntelliSense para Auxiliares de Marca apresenta detalhes sobre como trabalhar com
Auxiliares de Marca no editor do Visual Studio.

Comparação entre Auxiliares de Marca e


Controles de Servidor Web
Os Auxiliares de Marca não têm o elemento ao qual estão associados;
simplesmente participam da renderização do elemento e do conteúdo. ASP.NET
Controles do Servidor Web são declarados e invocados em uma página.

ASP.NET Controles de Servidor Web têm um ciclo de vida não trivial que pode
dificultar o desenvolvimento e a depuração.

Os controles de Servidor Web permitem que você adicione a funcionalidade aos


elementos DOM (Modelo de Objeto do Documento) do cliente usando um
controle de cliente. Os Auxiliares de Marca não tem nenhum DOM.

Os controles de Servidor Web incluem a detecção automática do navegador. Os


Auxiliares de Marca não têm nenhum conhecimento sobre o navegador.

Vários Auxiliares de Marca podem agir no mesmo elemento (consulte Evitar


conflitos do Auxiliar de Marca) enquanto você normalmente não pode compor
controles do Servidor Web.

Os Auxiliares de Marca podem modificar a marca e o conteúdo de elementos


HTML no escopo com o qual foram definidos, mas não modificam diretamente
todo o resto em uma página. Os controles de Servidor Web têm um escopo menos
específico e podem executar ações que afetam outras partes da página,
permitindo efeitos colaterais não intencionais.

Os controles de Servidor Web usam conversores de tipo para converter cadeias de


caracteres em objetos. Com os Auxiliares de Marca, você trabalha nativamente no
C# e, portanto, não precisa fazer a conversão de tipo.

Os controles do Servidor Web usam System.ComponentModel para implementar o


comportamento de tempo de execução e tempo de design de componentes e
controles. System.ComponentModel inclui as interfaces e as classes base para
implementar atributos e conversores de tipo, associar a fontes de dados e licenciar
componentes. Compare isso com os Auxiliares de Marca, que normalmente são
derivados de TagHelper , e a classe base TagHelper expõe apenas dois métodos,
Process e ProcessAsync .
Personalizando a fonte de elemento do Auxiliar
de Marca
Você pode personalizar a fonte e a colorização deFontes e Cores doAmbiente>
deOpções> de Ferramentas>:

Auxiliares de marcação internos do ASP.NET


Core
Auxiliar de marcação de âncora

Auxiliar de Marcação de cache

Auxiliar de Marcação de componente

Auxiliar de Marcação de cache distribuído

Auxiliar de marcação de ambiente


Auxiliar de Marcação de formulário

Auxiliar de marcação de ação de formulário

Auxiliar de marcação de imagem

Auxiliar de marcação de entrada

Auxiliar de marcação de rótulo

Auxiliar de Marcação de link

Auxiliar de marca parcial

Auxiliar de Marcação de script

Auxiliar de Marcação de seleção

Auxiliar de Marcação de Textarea

Auxiliar de marcação de mensagem de validação

Resumo de validação de auxiliar de marcação

Recursos adicionais
Auxiliares de marca de autor
Trabalhar 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
Artigo • 28/11/2022 • 17 minutos para o fim da leitura

De Rick Anderson

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

Introdução aos Auxiliares de Marca


Este tutorial fornece uma introdução à programação de auxiliares de marcação.
Introdução aos auxiliares de marcação descreve os benefícios que os auxiliares de
marcação fornecem.

Um auxiliar de marca é qualquer classe que implementa a interface ITagHelper . No


entanto, quando cria um auxiliar de marca, você geralmente deriva de TagHelper , o que
fornece acesso ao método Process .

1. Crie um novo projeto do ASP.NET Core chamado AuthoringTagHelpers. Você não


precisará de autenticação para esse projeto.

2. Criar uma pasta para armazenar os auxiliares de marca chamados TagHelpers. A


pasta TagHelpers pasta não é necessária, mas é uma convenção comum. Agora
vamos começar a escrever alguns auxiliares de marca simples.

Um auxiliar de marca mínimo


Nesta seção, você escreve um auxiliar de marca que atualiza uma marca de email. Por
exemplo:

HTML

<email>Support</email>

O servidor usará nosso auxiliar de marca de email para converter essa marcação como a
seguir:

HTML

<a href="mailto:Support@contoso.com">Support@contoso.com</a>
Ou seja, uma marca de âncora que torna isso um link de email. Talvez você deseje fazer
isso se estiver escrevendo um mecanismo de blog e precisar que ele envie emails para o
marketing, suporte e outros contatos, todos para o mesmo domínio.

1. Adicione a classe EmailTagHelper a seguir à pasta TagHelpers.

C#

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.

A classe EmailTagHelper deriva de TagHelper . O TagHelper classe fornece


métodos e propriedades para gravar tag helpers.

O método Process substituído controla o que o auxiliar de marca faz quando


é executado. A classe TagHelper também fornece uma versão assíncrona
( ProcessAsync ) com os mesmos parâmetros.

O parâmetro de contexto para Process (e ProcessAsync ) contém informações


associadas à execução da marca HTML atual.

O parâmetro de saída para Process (e ProcessAsync ) contém um elemento


HTML com estado que representa a fonte original usada para gerar uma
marca HTML e o conteúdo.
Nosso nome de classe tem um sufixo TagHelper, que não é necessário, mas
que é considerado uma convenção de melhor prática. Você pode declarar a
classe como:

C#

public class Email : TagHelper

2. Para disponibilizar a EmailTagHelper classe a todos os nossos Razor modos de


exibição, adicione a addTagHelper diretiva ao Views/_ViewImports.cshtml arquivo:

CSHTML

@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers

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 traz os auxiliares de marca MVC
ASP.NET Core usando a sintaxe curinga (esses auxiliares são discutidos em
Introdução aos Auxiliares de Marca.) É a @addTagHelper diretiva que disponibiliza o
auxiliar de marca para o modo de exibiçãoRazor. Como alternativa, você pode
fornecer o FQN (nome totalmente qualificado) de um auxiliar de marca, conforme
mostrado abaixo:

C#

@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 Views/Home/Contact.cshtml arquivo com estas alterações:

CSHTML

@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>

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 Razor exibição (na
forma de um mail-to atributo) e usá-la na geração da âncora.

Atualize a classe EmailTagHelper com o seguinte:

C#

public class EmailTagHelper : TagHelper


{
private const string EmailDomain = "contoso.com";

// Can be passed via <email mail-to="..." />.


// PascalCase gets translated into kebab-case.
public string MailTo { get; set; }

public override void Process(TagHelperContext context, TagHelperOutput


output)
{
output.TagName = "a"; // Replaces <email> with <a> tag

var address = MailTo + "@" + EmailDomain;


output.Attributes.SetAttribute("href", "mailto:" + address);
output.Content.SetContent(address);
}
}

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:

C#

public override void Process(TagHelperContext context, TagHelperOutput


output)
{
output.TagName = "a"; // Replaces <email> with <a> tag

var address = MailTo + "@" + EmailDomain;


output.Attributes.SetAttribute("href", "mailto:" + address);
output.Content.SetContent(address);
}

Essa abordagem funciona para o atributo "href" como no momento, ele não existe na
coleção de atributos. Você também pode usar o output.Attributes.Add para adicionar
um atributo do tag helper ao final da coleção de atributos de marca.

1. Atualize a marcação no Views/Home/Contact.cshtml arquivo com estas alterações:

CSHTML

@{
ViewData["Title"] = "Contact Copy";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<address>
One Microsoft Way Copy Version <br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong><email mail-to="Support"></email><br />
<strong>Marketing:</strong><email mail-to="Marketing"></email>
</address>

2. Execute o aplicativo e verifique se ele gera os links corretos.

7 Observação

Se você pretende escrever o autofechamento da marca de email ( <email mail-


to="Rick" /> ), a saída final também é o autofechamento. Para habilitar a
capacidade de gravar a marca com apenas uma marca inicial ( <email mail-
to="Rick"> ) você deve marcar a classe com o seguinte:

C#

[HtmlTargetElement("email", TagStructure = TagStructure.WithoutEndTag)]


public class EmailVoidTagHelper : TagHelper
{
private const string EmailDomain = "contoso.com";
// Code removed for brevity

Com um auxiliar de marca da marca de email com autofechamento, a saída será <a
href="mailto:Rick@contoso.com" /> . As marcas de âncora com autofechamento são um

HTML inválido. Portanto, não é recomendável criá-las, mas talvez criar um auxiliar de
marca com autofechamento. Auxiliares de marca definem o tipo da propriedade
TagMode após a leitura de uma marca.

Você também pode mapear um nome de atributo diferente para uma propriedade
usando o [HtmlAttributeName] atributo.

Para mapear um atributo nomeado recipient para a MailTo propriedade:

C#

[HtmlAttributeName("recipient")]
public string? MailTo { get; set; }

Auxiliar de marca para o recipient atributo:

HTML
<email recipient="…"/>

ProcessAsync
Nesta seção, escreveremos um auxiliar de email assíncrono.

1. Substitua a classe EmailTagHelper pelo seguinte código:

C#

public class EmailTagHelper : TagHelper


{
private const string EmailDomain = "contoso.com";
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "a"; //
Replaces <email> with <a> tag
var content = await output.GetChildContentAsync();
var target = content.GetContent() + "@" + EmailDomain;
output.Attributes.SetAttribute("href", "mailto:" + target);
output.Content.SetContent(target);
}
}

Observações:

Essa versão usa o método ProcessAsync assíncrono. O GetChildContentAsync


assíncrono retorna uma Task que contém o TagHelperContent .

Use o parâmetro output para obter o conteúdo do elemento HTML.

2. Faça a seguinte alteração no Views/Home/Contact.cshtml arquivo para que o


auxiliar de marca possa obter o email de destino.

CSHTML

@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>

3. Execute o aplicativo e verifique se ele gera links de email válidos.

RemoveAll, PreContent.SetHtmlContent e
PostContent.SetHtmlContent
1. Adicione a classe BoldTagHelper a seguir à pasta TagHelpers.

C#

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

O atributo [HtmlTargetElement] passa um parâmetro de atributo que


especifica que qualquer elemento HTML que contenha um atributo HTML
nomeado "bold" terá uma correspondência e que o método de substituição
Process na classe será executado. Em nossa amostra, o método Process

remove o atributo "bold" e envolve a marcação contida com <strong>


</strong> .

Como você não deseja substituir a conteúdo de marca existente, você precisa
escrever a marca <strong> de abertura com o método
PreContent.SetHtmlContent e a marca </strong> de fechamento com o

método PostContent.SetHtmlContent .
2. Modifique a exibição About.cshtml para conter um bold valor de atributo. O
código completo é mostrado abaixo.

CSHTML

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

<p bold>Use this area to provide additional information.</p>

<bold> Is this bold?</bold>

3. Execute o aplicativo. Use seu navegador favorito para inspecionar a origem e


verificar a marcação.

O [HtmlTargetElement] atributo acima se destina somente a marcação HTML que


fornece um nome de atributo de "bold". O <bold> elemento não foi modificado
pelo tag helper.

4. Comente a linha de atributo [HtmlTargetElement] e ela usará como padrão as


marcações <bold> de direcionamento, ou seja, a marcação HTML do formato
<bold> . Lembre-se de que a convenção de nomenclatura padrão fará a

correspondência do nome da classe BoldTagHelper com as marcações <bold> .

5. Execute o aplicativo e verifique se a marca <bold> é processada pelo auxiliar de


marca.

A decoração de uma classe com vários atributos [HtmlTargetElement] resulta em um OR


lógico dos destinos. Por exemplo, o uso do código a seguir, uma marca de negrito ou
um atributo de negrito terá uma correspondência.

C#

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

C#

[HtmlTargetElement("bold", Attributes = "bold")]

Também use o [HtmlTargetElement] para alterar o nome do elemento de destino. Por


exemplo, se você deseja que o BoldTagHelper seja direcionado a marcações <MyBold> ,
use o seguinte atributo:

C#

[HtmlTargetElement("MyBold")]

Passe um model para um tag helper


1. Adicionar uma pasta models.

2. Adicione a seguinte classe WebsiteContext à pasta Models:

C#

using System;

namespace AuthoringTagHelpers.Models
{
public class WebsiteContext
{
public Version Version { get; set; }
public int CopyrightYear { get; set; }
public bool Approved { get; set; }
public int TagsToShow { get; set; }
}
}

3. Adicione a classe WebsiteInformationTagHelper a seguir à pasta TagHelpers.

C#
using System;
using AuthoringTagHelpers.Models;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace AuthoringTagHelpers.TagHelpers
{
public class WebsiteInformationTagHelper : TagHelper
{
public WebsiteContext Info { get; set; }

public override void Process(TagHelperContext context,


TagHelperOutput output)
{
output.TagName = "section";
output.Content.SetHtmlContent(
$@"<ul><li><strong>Version:</strong> {Info.Version}</li>
<li><strong>Copyright Year:</strong> {Info.CopyrightYear}</li>
<li><strong>Approved:</strong> {Info.Approved}</li>
<li><strong>Number of tags to show:</strong> {Info.TagsToShow}</li>
</ul>");
output.TagMode = TagMode.StartTagAndEndTag;
}
}
}

Conforme mencionado anteriormente, os auxiliares de marcações convertem


nomes de classes e de propriedades dos auxiliares de marcações do C# na
formatação Pascal Case em Kebab Case . Portanto, para usar o
WebsiteInformationTagHelper in Razor, você escreverá <website-information

/> .

Você não identifica de forma explícita o elemento de destino com o atributo


[HtmlTargetElement] . Portanto, o padrão de website-information será o

destino. Se você aplicou o seguinte atributo (observe que não está em kebab
case, mas corresponde ao nome da classe):

C#

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

C#

[HtmlTargetElement("Website-Information")]
Os elementos com autofechamento não têm nenhum conteúdo. Para este
exemplo, a Razor marcação usará uma marca de auto-fechamento, mas o
auxiliar de marca criará um elemento de seção (que não é auto-
fechamento e você está escrevendo conteúdo dentro do section elemento).
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:

CSHTML

$@"<ul><li><strong>Version:</strong> {Info.Version}</li>

4. Adicione a marcação a seguir ao modo de exibição About.cshtml . A marcação


realçada exibe as informações do site.

CSHTML

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

<p bold>Use this area to provide additional information.</p>

<bold> Is this bold?</bold>

<h3> web site info </h3>


<website-information info="webContext" />

7 Observação

Na marcação Razor mostrada abaixo:

HTML
<website-information info="webContext" />

Razor sabe que o info atributo é uma classe, não uma cadeia de caracteres, e
você deseja escrever 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.

7 Observação

Você pode usar a seguinte marcação com uma marca de fechamento e


remova a linha com TagMode.StartTagAndEndTag no tag helper:

HTML

<website-information info="webContext" >


</website-information>

Tag helper condicional


O auxiliar de marca de condição renderiza a saída quando recebe um valor true.

1. Adicione a classe ConditionTagHelper a seguir à pasta TagHelpers.

C#

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace AuthoringTagHelpers.TagHelpers
{
[HtmlTargetElement(Attributes = nameof(Condition))]
public class ConditionTagHelper : TagHelper
{
public bool Condition { get; set; }

public override void Process(TagHelperContext context,


TagHelperOutput output)
{
if (!Condition)
{
output.SuppressOutput();
}
}
}
}

2. Substitua o conteúdo do Views/Home/Index.cshtml arquivo pela seguinte


marcação:

CSHTML

@using AuthoringTagHelpers.Models
@model WebsiteContext

@{
ViewData["Title"] = "Home Page";
}

<div>
<h3>Information about our website (outdated):</h3>
<Website-InforMation info="Model" />
<div condition="Model.Approved">
<p>
This website has <strong
surround="em">@Model.Approved</strong> been approved yet.
Visit www.contoso.com for more information.
</p>
</div>
</div>

3. Substitua o método Index no controlador Home pelo seguinte código:

C#

public IActionResult Index(bool approved = false)


{
return View(new WebsiteContext
{
Approved = approved,
CopyrightYear = 2015,
Version = new Version(1, 3, 3, 7),
TagsToShow = 20
});
}

4. Execute o aplicativo e navegue para a home page. A marcação no div condicional


não será renderizada. Acrescente a cadeia de caracteres de consulta ?
approved=true à URL (por exemplo, http://localhost:1235/Home/Index?
approved=true ). approved é definido como verdadeiro e a marcação condicional

será exibida.
7 Observação

Use o nameof operador para especificar o atributo de destino em vez de


especificar uma cadeia de caracteres como você fez com o tag helper em negrito:

C#

[HtmlTargetElement(Attributes = nameof(Condition))]
// [HtmlTargetElement(Attributes = "condition")]
public class ConditionTagHelper : TagHelper
{
public bool Condition { get; set; }

public override void Process(TagHelperContext context,


TagHelperOutput output)
{
if (!Condition)
{
output.SuppressOutput();
}
}
}

O operador nameof protegerá o código, caso ele seja refatorado (recomendamos


alterar o nome para RedCondition ).

Evitar conflitos de tag helper


Nesta seção, você escreve um par de tag helpers de vinculação automática. O primeiro
substituirá a marcação que contém uma URL iniciada por HTTP para um HTML âncora
marca que contém a mesma URL (e, portanto, resultando em um link de URL). O
segundo fará o mesmo para uma URL começando com WWW.

Como esses dois auxiliares estão intimamente relacionados e você poderá refatorá-los
no futuro, vamos mantê-los no mesmo arquivo.

1. Adicione a classe AutoLinkerHttpTagHelper a seguir à pasta TagHelpers.

C#

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

7 Observação

A classe AutoLinkerHttpTagHelper é direcionada a elementos p e usa o Regex


para criar a âncora.

2. Adicione a seguinte marcação ao final do Views/Home/Contact.cshtml arquivo:

CSHTML

@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>

<p>Visit us at http://docs.asp.net or at www.microsoft.com</p>

3. Execute o aplicativo e verifique se o tag helper renderiza a âncora corretamente.

4. Atualize a classe AutoLinker para incluir o AutoLinkerWwwTagHelper que converterá


o texto www em uma marca de âncora que também contém o texto www original.
O código atualizado é realçado abaixo:

C#
[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:

C#

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

7 Observação

Na primeira edição os tag helpers de vinculação automática, você obteve o


conteúdo do destino com o código a seguir:

C#

var childContent = await output.GetChildContentAsync();

Ou seja, você chama GetChildContentAsync usando a TagHelperOutput


passada para o método ProcessAsync . Conforme mencionado anteriormente,
como a saída é armazenada em cache, o último auxiliar de marca a ser
executado vence. Você corrigiu o problema com o seguinte código:
C#

var childContent = output.Content.IsModified ?


output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();

O código acima verifica se o conteúdo foi modificado e, em caso afirmativo,


ele obtém o conteúdo do buffer de saída.

6. Execute o aplicativo e verifique se os dois links funcionam conforme esperado.


Embora possa parecer que nosso auxiliar de marca de vinculador automático está
correto e completo, ele tem um problema sutil. Se o auxiliar de marca WWW for
executado primeiro, os links www não estarão corretos. Atualize o código
adicionando a sobrecarga Order para controlar a ordem em que a marca é
executada. A propriedade Order determina a ordem de execução em relação aos
outros auxiliares de marca direcionados ao mesmo elemento. O valor de ordem
padrão é zero e as instâncias com valores mais baixos são executadas primeiro.

C#

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.

Inspecionar e recuperar o conteúdo filho


Os tag helpers fornecem várias propriedades para recuperar o conteúdo.

O resultado de GetChildContentAsync pode ser acrescentado ao output.Content .


Inspecione o resultado de GetChildContentAsync com GetContent .
Se você modificar output.Content , o corpo da TagHelper não será executado ou
renderizado, a menos que você chame GetChildContentAsync como em nossa
amostra de vinculador automático:
C#

public class AutoLinkerHttpTagHelper : TagHelper


{
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
var childContent = output.Content.IsModified ?
output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();

// Find Urls in the content and replace them with their anchor tag
equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link
version}
}
}

Várias chamadas a GetChildContentAsync retornam o mesmo valor e não executam


o corpo TagHelper novamente, a menos que você passe um parâmetro falso
indicando para não usar o resultado armazenado em cache.

Carregar a exibição parcial minimizada


TagHelper
Em ambientes de produção, o desempenho pode ser melhorado com o carregamento
de exibições parciais minimizadas. Para tirar proveito da exibição parcial minimizada em
produção:

Crie/configure um processo de pré-compilação que minimize as exibições parciais.


Use o código a seguir para carregar exibições parciais minificadas em ambientes
de não desenvolvimento.

C#

public class MinifiedVersionPartialTagHelper : PartialTagHelper


{
public MinifiedVersionPartialTagHelper(ICompositeViewEngine
viewEngine,
IViewBufferScope viewBufferScope)
: base(viewEngine, viewBufferScope)
{

}
public override Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
// Append ".min" to load the minified partial view.
if (!IsDevelopment())
{
Name += ".min";
}

return base.ProcessAsync(context, output);


}

private bool IsDevelopment()


{
return
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
==
EnvironmentName.Development;
}
}
Auxiliares de marca em formulários no
ASP.NET Core
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

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

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

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


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

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


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

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


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

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


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

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


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

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


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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
Artigo • 04/01/2023 • 6 minutos para o fim da leitura

Por Scott Addie e Fiyaz Bin Hasan

Um Componente do Auxiliar de Marca é um Auxiliar de Marca que permite que você


modifique ou adicione condicionalmente elementos HTML de código do lado do
servidor. Esse recurso está disponível no ASP.NET Core 2.0 ou posterior.

O ASP.NET Core inclui dois Componentes de Auxiliar de Marca internos: head e body .
Eles estão localizados no Microsoft.AspNetCore.Mvc.Razor.TagHelpers namespace e
podem ser usados em MVC e Razor páginas. Componentes auxiliares de marca não
exigem registro com o aplicativo em _ViewImports.cshtml .

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

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

As seções a seguir descrevem esses casos de uso.

Injetar no elemento HTML head


Dentro do elemento HTML <head> , os arquivos CSS são geralmente importados com o
elemento HTML <link> . O código a seguir injeta um elemento <link> no elemento
<head> usando o Componente do Auxiliar de Marca head :

C#

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

public override int Order => 1;

public override Task ProcessAsync(TagHelperContext context,


TagHelperOutput output)
{
if (string.Equals(context.TagName, "head",
StringComparison.OrdinalIgnoreCase))
{
output.PostContent.AppendHtml(_style);
}

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:

C#

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;

public override async Task ProcessAsync(TagHelperContext context,


TagHelperOutput output)
{
if (string.Equals(context.TagName, "body",
StringComparison.OrdinalIgnoreCase))
{
var script = await File.ReadAllTextAsync(
"TagHelpers/Templates/AddressToolTipScript.html");
output.PostContent.AppendHtml(script);
}
}
}
}

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
TagHelpers/Templates/AddressToolTipScript.html e o acrescenta com a saída do Auxiliar

de Marca. O AddressToolTipScript.html arquivo inclui a seguinte marcação:

HTML

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

Registro por contêiner de serviços


Registro via Razor arquivo
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:

C#

public void ConfigureServices(IServiceCollection services)


{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});

services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

services.AddTransient<ITagHelperComponent,
AddressScriptTagHelperComponent>();
services.AddTransient<ITagHelperComponent,
AddressStyleTagHelperComponent>();
}

Registro via Razor arquivo


Se o Componente auxiliar de marca não estiver registrado com DI, ele poderá ser
registrado em uma Razor página de Páginas ou em um modo de exibição MVC. Essa
técnica é usada para controlar a marcação injetada e a ordem de execução do
componente de um Razor arquivo.

ITagHelperComponentManager é usado para adicionar Componentes do Auxiliar de Marca


ou removê-los do aplicativo. O código a seguir demonstra essa técnica com
AddressTagHelperComponent :

CSHTML

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

manager.Components.Add(new AddressTagHelperComponent(markup, 1));


}

No código anterior:

A diretiva @inject fornece uma instância de ITagHelperComponentManager . A


instância é atribuída a uma variável nomeada manager para acesso downstream no
Razor arquivo.
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 :

C#

private readonly string _markup;

public override int Order { get; }

public AddressTagHelperComponent(string markup = "", int order = 1)


{
_markup = markup;
Order = order;
}

O parâmetro markup fornecido é usado em ProcessAsync da seguinte maneira:

C#

public override async Task ProcessAsync(TagHelperContext context,


TagHelperOutput output)
{
if (string.Equals(context.TagName, "address",
StringComparison.OrdinalIgnoreCase) &&
output.Attributes.ContainsName("printable"))
{
TagHelperContent childContent = await output.GetChildContentAsync();
string content = childContent.GetContent();
output.Content.SetHtmlContent(
$"<div>{content}<br>{_markup}</div>{_printableButton}");
}
}

Registro por Modelo de página ou controlador


Se o Componente auxiliar de marca não estiver registrado com DI, ele poderá ser
registrado em um Razor modelo de página de Páginas ou em um controlador MVC. Essa
técnica é útil para separar a lógica C# dos Razor arquivos.

A injeção do construtor é usada para acessar uma instância de


ITagHelperComponentManager . Um Componente do Auxiliar de Marca é adicionado à

coleção de Componentes do Auxiliar de Marca da instância. O modelo de página


Páginas Razor a seguir demonstra essa técnica com AddressTagHelperComponent :

C#

using System;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesSample.TagHelpers;

public class IndexModel : PageModel


{
private readonly ITagHelperComponentManager _tagHelperComponentManager;

public bool IsWeekend


{
get
{
var dayOfWeek = DateTime.Now.DayOfWeek;

return dayOfWeek == DayOfWeek.Saturday ||


dayOfWeek == DayOfWeek.Sunday;
}
}

public IndexModel(ITagHelperComponentManager tagHelperComponentManager)


{
_tagHelperComponentManager = tagHelperComponentManager;
}

public void OnGet()


{
string markup;

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 [HtmlTargetElement] atributo à classe. Especifique o nome do
elemento HTML de destino.
Opcional: aplique um [EditorBrowsable(EditorBrowsableState.Never)] atributo à
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> :

C#

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:

C#

public class AddressTagHelperComponent : TagHelperComponent


{
private readonly string _printableButton =
"<button type='button' class='btn btn-info' onclick=\"window.open("
+
"'https://binged.it/2AXRRYw')\">" +
"<span class='glyphicon glyphicon-road' aria-hidden='true'></span>"
+
"</button>";

public override int Order => 3;

public override async Task ProcessAsync(TagHelperContext context,


TagHelperOutput output)
{
if (string.Equals(context.TagName, "address",
StringComparison.OrdinalIgnoreCase) &&
output.Attributes.ContainsName("printable"))
{
var content = await output.GetChildContentAsync();
output.Content.SetHtmlContent(
$"<div>{content.GetContent()}</div>{_printableButton}");
}
}
}

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

CSHTML
<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
Artigo • 04/01/2023 • 7 minutos para o fim da leitura

De Peter Kellner e Scott Addie

O Auxiliar de Marca de Âncora aprimora a marca de âncora HTML padrão ( <a ... >
</a> ) adicionando novos atributos. Por convenção, os nomes de atributos são

prefixados com asp- . O valor do atributo href do elemento de âncora renderizado é


determinado pelos valores dos atributos asp- .

Para obter uma visão geral dos Auxiliares de Marca, consulte auxiliares de marca no
ASP.NET Core.

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

SpeakerController é usado em exemplos ao longo de todo este documento:

C#

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

public class SpeakerController : Controller


{
private List<Speaker> Speakers =
new List<Speaker>
{
new Speaker {SpeakerId = 10},
new Speaker {SpeakerId = 11},
new Speaker {SpeakerId = 12}
};

[Route("Speaker/{id:int}")]
public IActionResult Detail(int id) =>
View(Speakers.FirstOrDefault(a => a.SpeakerId == id));

[Route("/Speaker/Evaluations",
Name = "speakerevals")]
public IActionResult Evaluations() => View();

[Route("/Speaker/EvaluationsCurrent",
Name = "speakerevalscurrent")]
public IActionResult Evaluations(
int speakerId,
bool currentYear) => View();
public IActionResult Index() => View(Speakers);
}

public class Speaker


{
public int SpeakerId { get; set; }
}

Atributos do Auxiliar de Marca de Âncora

asp-controller
O atributo asp-controller designa o controlador usado para gerar a URL. A marcação a
seguir lista todos os palestrantes:

CSHTML

<a asp-controller="Speaker"
asp-action="Index">All Speakers</a>

O HTML gerado:

HTML

<a href="/Speaker">All Speakers</a>

Se o atributo asp-controller for especificado e asp-action não for, o valor asp-action


padrão é a ação do controlador associada ao modo de exibição em execução no
momento. Se asp-action for omitido da marcação anterior e o Auxiliar de Marca de
Âncora for usado na Homeexibição Índice do Controlador (/Home), o HTML gerado será:

HTML

<a href="/Home">All Speakers</a>

asp-action
O valor do atributo asp-action representa o nome da ação do controlador incluído no
atributo href gerado. A seguinte marcação define o valor do atributo href gerado para
a página de avaliações do palestrante:

CSHTML
<a asp-controller="Speaker"
asp-action="Evaluations">Speaker Evaluations</a>

O HTML gerado:

HTML

<a href="/Speaker/Evaluations">Speaker Evaluations</a>

Se nenhum atributo asp-controller for especificado, o controlador padrão que está


chamando a exibição que executa a exibição atual é usado.

Se o valor do atributo asp-action for Index , nenhuma ação será anexada à URL,
causando a invocação da ação Index padrão. A ação especificada (ou padrão), deve
existir no controlador referenciado em asp-controller .

asp-route-{value}
O atributo asp-route-{value} permite um prefixo de roteamento de caractere curinga.
Qualquer valor que esteja ocupando o espaço reservado {value} é interpretado como
um possível parâmetro de roteamento. Se uma rota padrão não for encontrada, esse
prefixo de rota será anexado ao atributo href gerado como um valor e parâmetro de
solicitação. Caso contrário, ele será substituído no modelo de rota.

Considere a seguinte ação do controlador:

C#

private List<Speaker> Speakers =


new List<Speaker>
{
new Speaker {SpeakerId = 10},
new Speaker {SpeakerId = 11},
new Speaker {SpeakerId = 12}
};

[Route("Speaker/{id:int}")]
public IActionResult Detail(int id) =>
View(Speakers.FirstOrDefault(a => a.SpeakerId == id));

Com um modelo de rota padrão definido em Startup.Configure:

C#
app.UseMvc(routes =>
{
// need route and attribute on controller: [Area("Blogs")]
routes.MapRoute(name: "mvcAreaRoute",
template: "
{area:exists}/{controller=Home}/{action=Index}");

// default route for non-areas


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

O modo de exibição MVC usa o modelo fornecido pela ação, da seguinte forma:

CSHTML

@model Speaker
<!DOCTYPE html>
<html>
<body>
<a asp-controller="Speaker"
asp-action="Detail"
asp-route-id="@Model.SpeakerId">SpeakerId: @Model.SpeakerId</a>
</body>
</html>

O espaço reservado {id?} da rota padrão foi correspondido. O HTML gerado:

HTML

<a href="/Speaker/Detail/12">SpeakerId: 12</a>

Suponha que o prefixo de roteamento não faça parte do modelo de roteamento de


correspondência, como com o seguinte modo de exibição de MVC:

CSHTML

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

HTML

<a href="/Speaker/Detail?speakerid=12">SpeakerId: 12</a>

Se asp-controller ou asp-action não forem especificados, o mesmo processamento


padrão será seguido, como no atributo asp-route .

asp-route
O atributo asp-route é usado para criar um link de URL diretamente para uma rota
nomeada. Usando atributos de roteamento, uma rota pode ser nomeada como
mostrado em SpeakerController e usada em sua ação Evaluations :

C#

[Route("/Speaker/Evaluations",
Name = "speakerevals")]

Na seguinte marcação, o atributo asp-route faz referência à rota nomeada:

CSHTML

<a asp-route="speakerevals">Speaker Evaluations</a>

O Auxiliar de Marca de Âncora gera uma rota diretamente para essa ação de
controlador usando a URL /Speaker/Evaluations. O HTML gerado:

HTML

<a href="/Speaker/Evaluations">Speaker Evaluations</a>

Se asp-controller ou asp-action for especificado além de asp-route , a rota gerada


poderá não ser a esperada. Para evitar um conflito de rota, asp-route não deve ser
usado com os atributos asp-controller ou asp-action .

asp-all-route-data
O atributo asp-all-route-data oferece suporte à criação de um dicionário ou par chave-
valor. A chave é o nome do parâmetro e o valor é o valor do parâmetro.
No exemplo a seguir, um dicionário é inicializado e passado para um modo de exibição
Razor . Como alternativa, os dados podem ser passado com seu modelo.

CSHTML

@{
var parms = new Dictionary<string, string>
{
{ "speakerId", "11" },
{ "currentYear", "true" }
};
}

<a asp-route="speakerevalscurrent"
asp-all-route-data="parms">Speaker Evaluations</a>

O código anterior gera o seguinte HTML:

HTML

<a href="/Speaker/EvaluationsCurrent?speakerId=11&currentYear=true">Speaker
Evaluations</a>

O dicionário asp-all-route-data é simplificado para produzir um querystring que


atenda aos requisitos da ação Evaluations sobrecarregada:

C#

public IActionResult Evaluations() => View();

[Route("/Speaker/EvaluationsCurrent",
Name = "speakerevalscurrent")]
public IActionResult Evaluations(

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:

CSHTML
<a asp-controller="Speaker"
asp-action="Evaluations"
asp-fragment="SpeakerEvaluations">Speaker Evaluations</a>

O HTML gerado:

HTML

<a href="/Speaker/Evaluations#SpeakerEvaluations">Speaker Evaluations</a>

As marcas de hash são úteis ao criar aplicativos do lado do cliente. Elas podem ser
usadas para marcar e pesquisar com facilidade em JavaScript, por exemplo.

asp-area
O atributo asp-area define o nome de área usado para definir a rota apropriada. O
exemplo a seguir ilustra como o atributo asp-area causa um remapeamento das rotas.

Uso em Razor Páginas


RazorHá suporte para áreas de páginas 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 à página índiceRazor da área de sessões é:

CSHTML

<a asp-area="Sessions"
asp-page="/Index">View Sessions</a>

O HTML gerado:
HTML

<a href="/Sessions">View Sessions</a>

 Dica

Para dar suporte a áreas em um Razor aplicativo Pages, siga um destes


procedimentos em Startup.ConfigureServices :

Defina a versão de compatibilidade para 2.1 ou posterior.

Para fazer isso, defina a RazorPagesOptions.AllowAreaspropriedade para


true :

C#

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
Home
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 é:
CSHTML

<a asp-area="Blogs"
asp-controller="Home"
asp-action="AboutBlog">About Blog</a>

O HTML gerado:

HTML

<a href="/Blogs/Home/AboutBlog">About Blog</a>

 Dica

Para dar suporte às áreas em um aplicativo MVC, o modelo de rota deverá incluir
uma referência à área, se ela existir. Esse modelo é representado pelo segundo
parâmetro da chamada de routes.MapRoute método em Startup.Configure:

C#

app.UseMvc(routes =>
{
// need route and attribute on controller: [Area("Blogs")]
routes.MapRoute(name: "mvcAreaRoute",
template: "
{area:exists}/{controller=Home}/{action=Index}");

// default route for non-areas


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

asp-protocol
O atributo asp-protocol é destinado a especificar um protocolo (como https ) em sua
URL. Por exemplo:

CSHTML

<a asp-protocol="https"
asp-controller="Home"
asp-action="About">About</a>
O HTML gerado:

HTML

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

CSHTML

<a asp-protocol="https"
asp-host="microsoft.com"
asp-controller="Home"
asp-action="About">About</a>

O HTML gerado:

HTML

<a href="https://microsoft.com/Home/About">About</a>

asp-page
O atributo de página asp é usado com Razor Páginas. Use-o para definir um valor de
atributo href da marca de âncora para uma página específica. Prefixar o nome da
página com / cria uma URL para uma página correspondente da raiz do aplicativo:

Com o código de exemplo, a marcação a seguir cria um link para a página do


participante Razor :

CSHTML

<a asp-page="/Attendee">All Attendees</a>

O HTML gerado:

HTML

<a href="/Attendee">All Attendees</a>


O atributo asp-page é mutuamente exclusivo com os atributos asp-route , asp-
controller e asp-action . No entanto, o asp-page pode ser usado com o asp-route-
{value} para controlar o roteamento, como mostra a marcação a seguir:

CSHTML

<a asp-page="/Attendee"
asp-route-attendeeid="10">View Attendee</a>

O HTML gerado:

HTML

<a href="/Attendee?attendeeid=10">View Attendee</a>

Se a página referenciada não existir, um link para a página atual será gerado usando um
valor ambiente da solicitação. Nenhum aviso é indicado, exceto no nível do log de
depuração.

asp-page-handler
O atributo asp-page-handler é usado com Razor Pages. Ele se destina à vinculação a
manipuladores de página específicos.

Considere o seguinte manipulador de página:

C#

public void OnGetProfile(int attendeeId)


{
ViewData["AttendeeId"] = attendeeId;

// code omitted for brevity


}

A marcação associada do modelo de página vincula ao manipulador de página


OnGetProfile . Observe que o prefixo On<Verb> do nome do método do manipulador de

página é omitido no valor do atributo asp-page-handler . Quando o método é


assíncrono, o sufixo Async também é omitido.

CSHTML
<a asp-page="/Attendee"
asp-page-handler="Profile"
asp-route-attendeeid="12">Attendee Profile</a>

O HTML gerado:

HTML

<a href="/Attendee?attendeeid=12&handler=Profile">Attendee Profile</a>

Recursos adicionais
Áreas no ASP.NET Core
Introdução às Razor páginas no ASP.NET Core
Versão de compatibilidade do ASP.NET Core MVC
Auxiliar de Marca de Cache no ASP.NET
Core MVC
Artigo • 04/01/2023 • 5 minutos para o fim da leitura

Por Peter Kellner

O Auxiliar de Marca de Cache possibilita melhorar o desempenho de seu aplicativo


ASP.NET Core armazenando seu conteúdo em cache no provedor de cache interno do
ASP.NET Core.

Para obter uma visão geral dos Auxiliares de Marca, consulte auxiliares de marca no
ASP.NET Core.

A marcação a seguir Razor armazena em cache a data atual:

CSHTML

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

Atributos do Auxiliar de Marca de Cache

Habilitado

Tipo de Atributo Exemplos Padrão

Boolean true , false true

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:

CSHTML

<cache enabled="true">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

expires-on

Tipo de Atributo Exemplo

DateTimeOffset @new DateTime(2025,1,29,17,02,0)

expires-on define uma data do término absoluta para o item em cache.

O exemplo a seguir armazena em cache o conteúdo do Auxiliar de Marca de Cache até


às 17:02 de 29 de janeiro de 2025:

CSHTML

<cache expires-on="@new DateTime(2025,1,29,17,02,0)">


Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

expires-after

Tipo de Atributo Exemplo Padrão

TimeSpan @TimeSpan.FromSeconds(120) 20 minutos

expires-after define o tempo decorrido desde a primeira solicitação para armazenar o

conteúdo em cache.

Exemplo:

CSHTML

<cache expires-after="@TimeSpan.FromSeconds(120)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

O Razor Mecanismo de Exibição define o valor padrão expires-after como vinte


minutos.

expires-sliding

Tipo de Atributo Exemplo


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:

CSHTML

<cache expires-sliding="@TimeSpan.FromSeconds(60)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-header

Tipo de Atributo Exemplos

String User-Agent , User-Agent,content-encoding

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:

CSHTML

<cache vary-by-header="User-Agent">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-query

Tipo de Atributo Exemplos

String Make , Make,Model

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:

CSHTML

<cache vary-by-query="Make,Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-route

Tipo de Atributo Exemplos

String Make , Make,Model

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 :

C#

routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{Make?}/{Model?}");

Index.cshtml :

CSHTML

<cache vary-by-route="Make,Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-cookie

Tipo de Exemplos
Atributo

String .AspNetCore.Identity.Application ,
.AspNetCore.Identity.Application,HairColor
vary-by-cookie aceita uma lista delimitada por vírgula de cookie nomes que disparam

uma atualização de cache quando os cookie valores são alterados.

O exemplo a seguir monitora o cookie associado ao ASP.NET Core Identity. Quando um


usuário é autenticado, uma alteração no Identitycookie gatilho dispara uma atualização
de cache:

CSHTML

<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

Boolean true , false true

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 de Contexto de Solicitação e pode ser exibido em um Razor modo de exibição
@User.Identity.Name fazendo referência.

O exemplo a seguir monitora o usuário conectado atual para disparar uma atualização
de cache:

CSHTML

<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 exclusivo cookie é
gerado quando um usuário é autenticado. O cache é mantido para o estado anônimo
quando não cookie está presente ou expirado cookie . Se o usuário não estiver
autenticado, o cache será mantido.

vary-by
Tipo de Atributo Exemplo

String @Model

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:

C#

public IActionResult Index(string myParam1, string myParam2, string


myParam3)
{
int num1;
int num2;
int.TryParse(myParam1, out num1);
int.TryParse(myParam2, out num2);
return View(viewName, num1 + num2);
}

Index.cshtml :

CSHTML

<cache vary-by="@Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

priority

Tipo de Atributo Exemplos Padrão

CacheItemPriority High , Low , NeverRemove , Normal Normal


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:

CSHTML

<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
Cache na memória no ASP.NET Core
Introdução ao Identity ASP.NET Core
Auxiliar de marca de componente no
ASP.NET Core
Artigo • 04/01/2023 • 5 minutos para o fim da leitura

Pré-requisitos
Siga as diretrizes na seção Configuração para:

Blazor Server: integre componentes roteáveis e não roteáveis Razor em Razor


aplicativos Pages e MVC.
Blazor WebAssembly: integre Razor componentes de uma solução hospedada
Blazor WebAssembly em Razor aplicativos Pages e MVC.

Auxiliar de marca de componente


Para renderizar um componente de uma página ou exibição, use o Auxiliar de Marca de
Componente ( <component> marca).

7 Observação

A integração de Razor componentes em Razor páginas e aplicativos MVC em um


aplicativo hospedado Blazor WebAssembly tem suporte em ASP.NET Core no .NET
5.0 ou posterior.

RenderMode configura se o componente:

É pré-gerado na página.
É renderizado como HTML estático na página ou se ele inclui as informações
necessárias para inicializar um Blazor aplicativo do agente do usuário.

Blazor WebAssembly Os modos de renderização do aplicativo são mostrados na tabela


a seguir.

Modo renderização Descrição

WebAssembly Renderiza um marcador para um Blazor WebAssembly aplicativo para


uso para incluir um componente interativo quando carregado no
navegador. O componente não é pré-gerado. Essa opção facilita a
renderização de componentes diferentes Blazor WebAssembly em
páginas diferentes.
Modo renderização Descrição

WebAssemblyPrerendered Pré-remete o componente para HTML estático e inclui um marcador


para um Blazor WebAssembly aplicativo para uso posterior para tornar
o componente interativo quando carregado no navegador.

Blazor Server Os modos de renderização do aplicativo são mostrados na tabela a seguir.

Modo Descrição
renderização

ServerPrerendered Renderiza o componente em HTML estático e inclui um marcador para um


Blazor Server aplicativo. Quando o usuário-agente é iniciado, esse marcador
é usado para inicializar um Blazor aplicativo.

Server Renderiza um marcador para um Blazor Server aplicativo. A saída do


componente não está incluída. Quando o usuário-agente é iniciado, esse
marcador é usado para inicializar um Blazor aplicativo.

Static Renderiza o componente em HTML estático.

As características adicionais incluem:

Vários auxiliares de marca de componente que renderizam vários Razor


componentes são permitidos.
Os componentes não podem ser renderizados dinamicamente após o início do
aplicativo.
Embora páginas e exibições possam usar componentes, o inverso não é
verdadeiro. Os componentes não podem usar recursos específicos de exibição e
página, como exibições parciais e seções. Para usar a lógica de uma exibição
parcial em um componente, fatore a lógica de exibição parcial em um
componente.
Não há suporte para a renderização de componentes do servidor de uma página
HTML estática.

O auxiliar de marca de componente a seguir renderiza o Counter componente em uma


página ou exibição em um Blazor Server aplicativo com ServerPrerendered :

CSHTML

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using {APP ASSEMBLY}.Pages

...

<component type="typeof(Counter)" render-mode="ServerPrerendered" />


O exemplo anterior pressupõe que o Counter componente esteja na pasta Páginas do
aplicativo. O espaço reservado {APP ASSEMBLY} é o nome do assembly do aplicativo (por
exemplo, @using BlazorSample.Pages ou @using BlazorSample.Client.Pages em uma
solução hospedada Blazor ).

O Auxiliar de Marca de Componente também pode passar parâmetros para


componentes. Considere o seguinte ColorfulCheckbox componente que define a cor e o
tamanho do rótulo da caixa de seleção:

razor

<label style="font-size:@(Size)px;color:@Color">
<input @bind="Value"
id="survey"
name="blazor"
type="checkbox" />
Enjoying Blazor?
</label>

@code {
[Parameter]
public bool Value { get; set; }

[Parameter]
public int Size { get; set; } = 8;

[Parameter]
public string Color { get; set; }

protected override void OnInitialized()


{
Size += 10;
}
}

Os Size parâmetros de componente ( int ) e Color ( string ) podem ser definidos pelo
Auxiliar de Marca de Componente:

CSHTML

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using {APP ASSEMBLY}.Shared

...

<component type="typeof(ColorfulCheckbox)" render-mode="ServerPrerendered"


param-Size="14" param-Color="@("blue")" />
O exemplo anterior pressupõe que o ColorfulCheckbox componente esteja na pasta
Compartilhada do aplicativo. O espaço reservado {APP ASSEMBLY} é o nome do
assembly do aplicativo (por exemplo, @using BlazorSample.Shared ).

O HTML a seguir é renderizado na página ou exibição:

HTML

<label style="font-size:24px;color:blue">
<input id="survey" name="blazor" type="checkbox">
Enjoying Blazor?
</label>

Passar uma cadeia de caracteres entre aspas requer uma expressão explícitaRazor,
conforme mostrado param-Color no exemplo anterior. O Razor comportamento de
análise de um string valor de tipo não se aplica a um param-* atributo porque o
atributo é um object tipo.

Todos os tipos de parâmetros têm suporte, exceto:

Parâmetros genéricos.
Parâmetros não serializáveis.
Herança em parâmetros de coleção.
Parâmetros cujo tipo é definido fora do Blazor WebAssembly aplicativo ou dentro
de um assembly com preguiçosamente carregado.
Para receber um RenderFragment delegado para conteúdo filho (por exemplo,
param-ChildContent="..." ). Para esse cenário, é recomendável criar um Razor
componente ( .razor ) que faça referência ao componente que você deseja
renderizar com o conteúdo filho que você deseja passar e, em seguida, invocar o
Razor componente da página ou exibição com o Auxiliar de Marca de
Componente.

O tipo de parâmetro deve ser JSON serializável, o que normalmente significa que o tipo
deve ter um construtor padrão e propriedades configuráveis. Por exemplo, você pode
especificar um valor para Size e Color no exemplo anterior porque os tipos de Size e
Color são tipos primitivos ( int e string ), que são compatíveis com o JSserializador
ON.

No exemplo a seguir, um objeto de classe é passado para o componente:

MyClass.cs :

C#
public class MyClass
{
public MyClass()
{
}

public int MyInt { get; set; } = 999;


public string MyString { get; set; } = "Initial value";
}

A classe deve ter um construtor público sem parâmetros.

Shared/MyComponent.razor :

razor

<h2>MyComponent</h2>

<p>Int: @MyObject.MyInt</p>
<p>String: @MyObject.MyString</p>

@code
{
[Parameter]
public MyClass MyObject { get; set; }
}

Pages/MyPage.cshtml :

CSHTML

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using {APP ASSEMBLY}
@using {APP ASSEMBLY}.Shared

...

@{
var myObject = new MyClass();
myObject.MyInt = 7;
myObject.MyString = "Set by MyPage";
}

<component type="typeof(MyComponent)" render-mode="ServerPrerendered"


param-MyObject="@myObject" />

O exemplo anterior pressupõe que o MyComponent componente esteja na pasta


Compartilhada do aplicativo. O espaço reservado {APP ASSEMBLY} é o nome do
assembly do aplicativo (por exemplo, @using BlazorSample e @using
BlazorSample.Shared ). MyClass está no namespace do aplicativo.

Recursos adicionais
ComponentTagHelper
Auxiliares de Marca no ASP.NET Core
Razor componentes ASP.NET Core
Auxiliar de Marca de Cache Distribuído
no ASP.NET Core
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

Por Peter Kellner

O Auxiliar de Marca de Cache Distribuído fornece a capacidade de melhorar


consideravelmente o desempenho do aplicativo ASP.NET Core armazenando seu
conteúdo em cache em uma fonte de cache distribuído.

Para obter uma visão geral dos Auxiliares de Marca, consulte Auxiliares de Marca no
ASP.NET Core.

O Auxiliar de Marca de Cache Distribuído herda da mesma classe base do Auxiliar de


Marca de Cache. Todos os atributos de Auxiliar de Marca de Cache estão disponíveis ao
Auxiliar de Marca Distribuído.

O Auxiliar de Marca de Cache distribuído usa injeção de construtor. A interface


IDistributedCache é passada para o construtor do Auxiliar de Marca de Cache
Distribuído. Se nenhuma implementação concreta for IDistributedCache criada no
Startup.ConfigureServices ( Startup.cs ), o Auxiliar de Marca de Cache Distribuído

usará o mesmo provedor na memória para armazenar dados armazenados em cache


que o Auxiliar de Marca de Cache.

Atributos do auxiliar de marca de cache


distribuído

Atributos compartilhados com o Auxiliar de Marca de


Cache
enabled

expires-on

expires-after
expires-sliding

vary-by-header
vary-by-query

vary-by-route

vary-by-cookie
vary-by-user

vary-by
priority

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

String my-distributed-cache-unique-key-101

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 Razor página e no local na Razor
página, o Auxiliar de Marca de Cache Distribuído baseia apenas sua chave no atributo
name .

Exemplo:

CSHTML

<distributed-cache name="my-distributed-cache-unique-key-101">
Time Inside Cache Tag Helper: @DateTime.Now
</distributed-cache>

Implementações de IDistributedCache do
Auxiliar de Marca de Cache Distribuído
Há duas implementações de IDistributedCache internas do ASP.NET Core. Uma é
baseada no SQL Server e a outra, no Redis. Implementações de terceiros também estão
disponíveis, como NCache . Os detalhes dessas implementações podem ser
encontrados no cache distribuído em ASP.NET Core. Ambas as implementações
envolvem configurar de uma instância do IDistributedCache em Startup .

Não há nenhum atributo de marca especificamente associado ao uso de uma


implementação específica de IDistributedCache .
Recursos adicionais
Auxiliar de Marca de Cache no ASP.NET Core MVC
Injeção de dependência no ASP.NET Core
Cache distribuído em ASP.NET Core
Cache na memória no ASP.NET Core
Introdução ao Identity ASP.NET Core
Auxiliar de Marca de Ambiente no
ASP.NET Core
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

Por Peter Kellner e Hisham Bin Ateya

O Auxiliar de Marca de Ambiente renderiza condicionalmente seu conteúdo fechado


com base no ambiente de hospedagem atual. Atributo único do Auxiliar de Marca de
Ambiente, names , é uma lista separada por vírgulas de nomes de ambiente. Se nenhum
dos nomes de ambiente fornecido corresponder ao ambiente atual, o conteúdo contido
será renderizado.

Para obter uma visão geral dos Auxiliares de Marca, consulte auxiliares de marca no
ASP.NET Core.

Atributos do Auxiliar de Marca de Ambiente

nomes
names aceita um único nome de ambiente de hospedagem ou uma lista separada por

vírgula de nomes de ambiente de hospedagem que disparam a renderização do


conteúdo contido.

Os valores de ambiente são comparados com o valor atual retornado por


IWebHostEnvironment.EnvironmentName. A comparação ignora o uso de maiúsculas.

O exemplo a seguir usa um Auxiliar de Marca de Ambiente. O conteúdo será


renderizado se o ambiente de hospedagem for De Preparo ou de Produção:

CSHTML

<environment names="Staging,Production">
<strong>IWebHostEnvironment.EnvironmentName is Staging or
Production</strong>
</environment>

incluir e excluir atributos


Os atributos include & exclude controlam a renderização do conteúdo contido com
base nos nomes de ambiente de hospedagem incluídos ou excluídos.
include
A propriedade include exibe um comportamento semelhante para o atributo names .
Um ambiente listado no valor do include atributo deve corresponder ao ambiente de
hospedagem do aplicativo (IWebHostEnvironment.EnvironmentName) para renderizar o
conteúdo da <environment> marca.

CSHTML

<environment include="Staging,Production">
<strong>IWebHostEnvironment.EnvironmentName is Staging or
Production</strong>
</environment>

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

CSHTML

<environment exclude="Development">
<strong>IWebHostEnvironment.EnvironmentName is not Development</strong>
</environment>

Recursos adicionais
Usar múltiplos ambientes no ASP.NET Core
Auxiliares de marca em formulários no
ASP.NET Core
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

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

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

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


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

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


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

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


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

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


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

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


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

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


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

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

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

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


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

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


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

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


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

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


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

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


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

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


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

Por Peter Kellner

O Auxiliar de Marca de Imagem aprimora a marca <img> para fornecer comportamento


de extrapolação de cache para arquivos de imagem estática.

Uma cadeia de caracteres de extrapolação de cache é um valor exclusivo que representa


o hash do arquivo de imagem estática acrescentado à URL do ativo. A cadeia de
caracteres exclusiva solicita que os clientes (e alguns proxies) recarreguem a imagem do
servidor Web do host, e não do cache do cliente.

Se a origem da imagem ( src ) for um arquivo estático no servidor Web de host:

Uma cadeia de caracteres exclusiva de extrapolação de cache será anexada como


um parâmetro de consulta à origem da imagem.
Se o arquivo no servidor Web host for alterado, será gerada uma URL de
solicitação exclusiva que inclua o parâmetro de solicitação atualizado.

Para obter uma visão geral dos Auxiliares de Marca, consulte Auxiliares de Marca no
ASP.NET Core.

Atributos de Auxiliar de Marca de Imagem

src
Para ativar o Auxiliar de Marca de Imagem, o atributo src é obrigatório no elemento
<img> .

A origem da imagem ( src ) deve apontar para um arquivo estático físico no servidor. Se
src for um URI remoto, o parâmetro de cadeia de caracteres de consulta de

extrapolação de cache não será gerado.

asp-append-version
Quando asp-append-version for especificado com um valor true junto com um atributo
src , o Auxiliar de Marca de Imagem será invocado.
O exemplo a seguir usa um Auxiliar de Marca de Imagem:

CSHTML

<img src="~/images/asplogo.png" asp-append-version="true">

Se o arquivo estático existe no diretório /wwwroot/images/, o HTML gerado é


semelhante ao seguinte (o hash será diferente):

HTML

<img src="/images/asplogo.png?
v=Kl_dqr9NVtnMdsM2MUg4qthUnWZm5T1fCEimBPWDNgM">

O valor atribuído ao parâmetro v é o valor de hash do asplogo.png arquivo no 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.

Para que um Auxiliar de Marca gere uma versão para um arquivo estático fora wwwroot ,
consulte Arquivos do Serve de vários locais

Comportamento de armazenamento em cache


de hash
O Auxiliar de Marca de Imagem usa o provedor de cache no servidor Web local para
armazenar o hash Sha512 calculado de um determinado arquivo. Se o arquivo for
solicitado várias vezes, o hash não será recalculado. O cache é invalidado por um
observador de arquivo anexado ao arquivo quando o hash Sha512 do arquivo é
calculado. Quando o arquivo muda no disco, um novo hash é calculado e armazenado
em cache.

Recursos adicionais
Cache na memória no ASP.NET Core
Auxiliares de marca em formulários no
ASP.NET Core
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

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

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

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


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

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


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

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


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

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


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

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


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

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


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

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

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

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


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

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


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

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


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

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


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

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


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

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


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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 link no ASP.NET
Core
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

De Rick Anderson

O Auxiliar de Marca de Link gera um link para um arquivo CSS primário ou de fall back.
Normalmente, o arquivo CSS primário está em uma CDN ( Rede de Distribuição de
Conteúdo ).

Uma CDN:

Fornece várias vantagens de desempenho versus hospedar o ativo com o


aplicativo Web.
Não deve ser confiada como a única fonte para o ativo. As CDNs nem sempre
estão disponíveis, portanto, um fallback confiável deve ser usado. Normalmente, o
fallback é o site que hospeda o aplicativo Web.

O Auxiliar de Marca de Link permite que você especifique uma CDN para o arquivo CSS
e um fallback quando a CDN não estiver disponível. O Auxiliar de Marca de Link fornece
a vantagem de desempenho de uma CDN com a robustez da hospedagem local.

A marcação a seguir Razor mostra o head elemento de um arquivo de layout criado


com o modelo de aplicativo Web ASP.NET Core:

CSHTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebLinkTH</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.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.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>

A seguir, é renderizado HTML do código anterior (em um ambiente não de


desenvolvimento):

HTML

<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Home page - WebLinkTH</title>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-
bootstrap/4.1.3/css/bootstrap.css"
crossorigin="anonymous" integrity="sha256-eS<snip>BE=" />
<meta name="x-stylesheet-fallback-test" content="" class="sr-only" />
<script>
!function (a, b, c, d) {
var e, f = document,
g = f.getElementsByTagName("SCRIPT"),
h = g[g.length - 1].previousElementSibling,
i = f.defaultView && f.defaultView.getComputedStyle ?
f.defaultView.getComputedStyle(h) : h.currentStyle;
if (i && i[a] !== b) for (e = 0; e < c.length; e++)
f.write('<link href="' + c[e] + '" ' + d + "/>")
}
("position", "absolute",
["\/lib\/bootstrap\/dist\/css\/bootstrap.css"],
"rel=\u0022stylesheet\u0022
crossorigin=\u0022anonymous\u0022 integrity=\abc<snip>BE=\u0022 ");
</script>

<link rel="stylesheet" href="/css/site.css" />


</head>

No código anterior, o Auxiliar de Marca de Link gerou o <meta name="x-stylesheet-


fallback-test" content="" class="sr-only" /> elemento e o JavaScript a seguir, que é

usado para verificar se o arquivo solicitado bootstrap.css está disponível na CDN.


Nesse caso, o arquivo CSS estava disponível para que o Auxiliar de Marca gerasse o
<link /> elemento com o arquivo CSS da CDN.
Atributos auxiliares de marca de link
comumente usados
Consulte o Auxiliar de Marca de Link para obter todos os atributos, propriedades e
métodos do Auxiliar de Marca de Link.

href
Endereço preferencial do recurso vinculado. O endereço é passado para o HTML gerado
em todos os casos.

asp-fallback-href
A URL de uma folha de estilos CSS para a qual fazer fallback, caso a URL primária falhe.

asp-fallback-test-class
O nome da classe definido na folha de estilos a ser usada para o teste de fallback. Para
obter mais informações, consulte FallbackTestClass.

asp-fallback-test-property
O nome da propriedade CSS a ser usado para o teste de fallback. Para obter mais
informações, consulte FallbackTestProperty.

asp-fallback-test-value
O valor da propriedade CSS a ser usado para o teste de fallback. Para obter mais
informações, consulte FallbackTestValue.

Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Áreas no ASP.NET Core
Introdução às Razor Páginas no ASP.NET Core
Versão de compatibilidade do ASP.NET Core MVC
Auxiliar de marca parcial no ASP.NET
Core
Artigo • 04/01/2023 • 3 minutos para o fim da leitura

Por Scott Addie

Para obter uma visão geral dos Auxiliares de Marca, consulte Auxiliares de Marca no
ASP.NET Core.

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

Visão geral
O Auxiliar de Marca Parcial é usado para renderizar uma exibição parcial em Razor
páginas e 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:

C#

namespace TagHelpersBuiltIn.Models
{
public class Product
{
public int Number { get; set; }

public string Name { get; set; }

public string Description { get; set; }


}
}

Segue um inventário dos atributos do auxiliar de marca parcial.


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 Compartilhada . Usando o atributo for, um modelo é
passado para a exibição parcial para associação.

CSHTML

<partial name="Shared/_ProductPartial.cshtml" for="Product">

para
O for atributo atribui um ModelExpression a ser avaliado 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 marcação a seguir carrega _ProductPartial.cshtml :

CSHTML

<partial name="_ProductPartial" for="Product">

A exibição parcial é associada à propriedade Product do modelo de página associado:

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using TagHelpersBuiltIn.Models;

namespace TagHelpersBuiltIn.Pages
{
public class ProductModel : PageModel
{
public Product Product { get; set; }

public void OnGet()


{
Product = new Product
{
Number = 1,
Name = "Test product",
Description = "This is a test product"
};
}
}
}

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:

CSHTML

<partial name="_ProductPartial"
model='new Product { Number = 1, Name = "Test product", Description
= "This is a test" }'>

view-data
O view-data atributo atribui um ViewDataDictionary para passar 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:

CSHTML

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

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

Neste exemplo, o valor de ViewData["IsNumberReadOnly"] determina se o campo


Número é exibido como somente leitura.

Migrar de um auxiliar HTML


Considere o seguinte exemplo de auxiliar HTML assíncrono. Uma coleção de produtos é
iterada e exibida. De acordo com o PartialAsync primeiro parâmetro do método, a
exibição _ProductPartial.cshtml parcial é carregada. Uma instância do modelo Product
é passada para a exibição parcial para associação.

CSHTML

@foreach (var product in Model.Products)


{
@await Html.PartialAsync("_ProductPartial", product)
}

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.

CSHTML
@foreach (var product in Model.Products)
{
<partial name="_ProductPartial" model="product" />
}

Recursos adicionais
Exibições parciais no ASP.NET Core
Exibições no ASP.NET Core MVC
Persistir Auxiliar de Marca de Estado do
Componente no ASP.NET Core
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Pré-requisitos
Siga as diretrizes na seção Configuração para:

Blazor WebAssembly
Blazor Server

Manter o estado para componentes pré-


gerados
Para manter o estado para componentes pré-gerados, use o Auxiliar de Marca de
Estado do Componente Persistente (origem de referência ). Adicione a marca do
Auxiliar de Marca, <persist-component-state /> , dentro da marca de fechamento
</body> da _Host página em um aplicativo que remete componentes.

7 Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam


o branch padrão do repositório, que representa o desenvolvimento atual da
próxima versão do .NET. Para selecionar uma marca para uma versão específica, use
a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como
selecionar uma marca de versão do código-fonte do ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Em Blazor WebAssembly aplicativos ( Pages/_Host.cshtml ):

CSHTML

<body>
<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />

...

<persist-component-state />
</body>
Em Blazor Server aplicativos ( Pages/_Host.cshtml ):

CSHTML

<body>
<component type="typeof(App)" render-mode="ServerPrerendered" />

...

<persist-component-state />
</body>

Decida qual estado persistir usando o PersistentComponentState serviço.


PersistentComponentState.RegisterOnPersisting registra um retorno de chamada para
persistir o estado do componente antes que o aplicativo seja pausado. O estado é
recuperado quando o aplicativo é retomado.

No exemplo a seguir:

O {TYPE} espaço reservado representa o tipo de dados a persistir (por exemplo,


WeatherForecast[] ).

O {TOKEN} espaço reservado é uma cadeia de caracteres de identificador de


estado (por exemplo, fetchdata ).

razor

@implements IDisposable
@inject PersistentComponentState ApplicationState

...

@code {
private {TYPE} data;
private PersistingComponentStateSubscription persistingSubscription;

protected override async Task OnInitializedAsync()


{
persistingSubscription =
ApplicationState.RegisterOnPersisting(PersistData);

if (!ApplicationState.TryTakeFromJson<{TYPE}>(
"{TOKEN}", out var restored))
{
data = await ...;
}
else
{
data = restored!;
}
}
private Task PersistData()
{
ApplicationState.PersistAsJson("{TOKEN}", data);

return Task.CompletedTask;
}

void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
}

Para obter mais informações e um exemplo completo, consulte Prerender e integre


ASP.NET Core Razor componentes.

Tamanho do estado pré-gerado e SignalR


limite de tamanho da mensagem
Esta seção só se aplica a Blazor Server aplicativos.

Um tamanho de estado pré-gerado grande pode exceder o SignalR limite de tamanho


da mensagem do circuito, o que resulta no seguinte:

O SignalR circuito falha ao inicializar com um erro no cliente: Circuit host not
initialized.
A caixa de diálogo de reconexão no cliente é exibida quando o circuito falha. A
recuperação não é possível.

Para resolver o problema, use uma das seguintes abordagens:

Reduza a quantidade de dados que você está colocando no estado pré-gerado.


Aumente o limite de tamanho daSignalR mensagem. AVISO: aumentar o limite
pode aumentar o risco de ataques de DoS (negação de serviço).

Recursos adicionais
ComponentTagHelper
Auxiliares de Marca no ASP.NET Core
Razor componentes do ASP.NET Core
Auxiliar de Marca de Script no ASP.NET
Core
Artigo • 14/12/2022 • 2 minutos para o fim da leitura

De Rick Anderson

O Auxiliar de Marca de Script gera um link para um arquivo de script primário ou de


fallback. Normalmente, o arquivo de script primário está em uma CDN ( Rede de
Distribuição de Conteúdo ).

Uma CDN:

Fornece várias vantagens de desempenho versus hospedar o ativo com o


aplicativo Web.
Não deve ser confiada como a única fonte para o ativo. As CDNs nem sempre
estão disponíveis, portanto, um fallback confiável deve ser usado. Normalmente, o
fallback é o site que hospeda o aplicativo Web.

O Auxiliar de Marca de Script permite que você especifique uma CDN para o arquivo de
script e um fallback quando a CDN não estiver disponível. O Auxiliar de Marca de Script
fornece a vantagem de desempenho de uma CDN com a robustez da hospedagem
local.

A marcação Razor a seguir mostra um script elemento com um fallback:

HTML

<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.js"
asp-fallback-src="~/lib/jquery/dist/jquery.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-
tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT">
</script>

Não use o <script> atributo de adiamento do elemento para adiar o carregamento


do script CDN. O Auxiliar de Marca de Script renderiza o JavaScript que executa
imediatamente a expressão asp-fallback-test . A expressão falhará se o carregamento do
script CDN for adiado.
Atributos auxiliares de marca de script
comumente usados
Consulte Auxiliar de Marca de Script para obter todos os atributos, propriedades e
métodos do Auxiliar de Marca de Script.

asp-append-version
Quando asp-append-version é especificado com um true valor junto com um src
atributo , uma versão exclusiva é gerada.

Para que um Auxiliar de Marca gere uma versão para um arquivo estático fora
wwwroot do , consulte Fornecer arquivos de vários locais

asp-fallback-src
A URL de uma marca script para a qual fazer fallback caso a principal falhe.

asp-fallback-src-exclude
Uma lista separada por vírgulas de padrões de arquivo globbed de scripts JavaScript a
serem excluídos da lista de fallback, caso o primário falhe. Os padrões glob são
avaliados em relação à configuração do webroot aplicativo. Deve ser usado em conjunto
com asp-fallback-src-include .

asp-fallback-src-include
Uma lista separada por vírgulas de padrões de arquivo com globbed de scripts
JavaScript para o qual fazer fallback caso o primário falhe. Os padrões glob são
avaliados em relação à configuração do webroot aplicativo.

asp-fallback-test
O método de script definido no script primário a ser usado para o teste de fallback. Para
obter mais informações, consulte FallbackTestExpression.

asp-order
Quando um conjunto de ITagHelper instâncias é executado, seus
Init(TagHelperContext) métodos são invocados primeiro na ordem especificada; em
seguida, seus ProcessAsync(TagHelperContext, TagHelperOutput) métodos são
invocados na ordem especificada. Os valores mais baixos são executados primeiro.

asp-src
Endereço do script externo a ser usado.

asp-src-exclude
Uma lista separada por vírgulas de padrões de arquivo com globbed de scripts
JavaScript a serem excluídos do carregamento. Os padrões glob são avaliados em
relação à configuração do webroot aplicativo. Deve ser usado em conjunto com asp-
src-include .

asp-src-include
Uma lista separada por vírgulas de padrões de arquivo com globbed de scripts
JavaScript a serem carregados. Os padrões glob são avaliados em relação à
configuração do webroot aplicativo.

asp-suppress-fallback-integrity
Valor booliano que determina se um hash de integridade será comparado com o valor
asp-fallback-src.

Recursos adicionais
Auxiliares de Marca no ASP.NET Core
Áreas no ASP.NET Core
Introdução às Razor Páginas no ASP.NET Core
Versão de compatibilidade do ASP.NET Core MVC
Auxiliares de marca em formulários no
ASP.NET Core
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

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

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

// If we got this far, something failed; redisplay form.


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="Index" method="post">


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

<form asp-controller="Home" asp-action="IndexEnum" method="post">


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

<form asp-controller="Home" asp-action="IndexMultiSelect" method="post">


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="IndexEmpty" method="post">


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="IndexEmpty" method="post">


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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; }
}
}

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

</table>
<button type="submit">Save</button>
</form>

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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; }
}
}

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

// If we got this far, something failed; redisplay form.


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="Index" method="post">


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

<form asp-controller="Home" asp-action="IndexEnum" method="post">


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

<form asp-controller="Home" asp-action="IndexMultiSelect" method="post">


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="IndexEmpty" method="post">


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="IndexEmpty" method="post">


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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; }
}
}

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

</table>
<button type="submit">Save</button>
</form>

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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; }
}
}

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

// If we got this far, something failed; redisplay form.


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="Index" method="post">


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

<form asp-controller="Home" asp-action="IndexEnum" method="post">


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

<form asp-controller="Home" asp-action="IndexMultiSelect" method="post">


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="IndexEmpty" method="post">


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="IndexEmpty" method="post">


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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
Artigo • 04/01/2023 • 21 minutos para o fim da leitura

De Rick Anderson , N. Taylor Mullen , Dave Paquette e Jerrie Pelser

Este documento demonstra como é o trabalho com Formulários e os elementos HTML


usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem
ajudar você a criar formulários HTML robustos de forma produtiva. É recomendável que
você leia Introdução ao auxiliares de marca antes de ler este documento.

Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um


Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


Auxiliar de Marca de Formulário:

Gera o valor do atributo HTML< FORM> action para uma ação de controlador

MVC ou rota nomeada

Gera um Token de verificação de solicitação oculto para evitar a falsificação de


solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)

Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é


adicionado aos valores de rota. Os routeValues parâmetros para Html.BeginForm e
Html.BeginRouteForm fornecem funcionalidades semelhantes.

Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Exemplo:

CSHTML

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>
O Auxiliar de marca de formulário acima gera o HTML a seguir:

HTML

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

O runtime 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 nomeada register pode usar a
seguinte marcação para a página de registro:

CSHTML

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

CSHTML

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

7 Observação
Com os modelos internos, returnUrl só é preenchido automaticamente quando
você tenta acessar um recurso autorizado, mas não está autenticado ou autorizado.
Quando você tenta fazer um acesso não autorizado, o middleware de segurança o
redireciona para a página de logon com o returnUrl definido.

Auxiliar de Marcação de Ação de Formulário


O Auxiliar de Marcação de Ação de Formulário gera o atributo formaction na marcação
<button ...> ou <input type="image" ...> gerada. O atributo formaction controla

onde um formulário envia seus dados. Ele se associa a <elementos de entrada> de


elementos de tipo image e <botão> . O Auxiliar de Marca de Ação de Formulário
permite o uso de vários atributos AnchorTagHelper asp- para controlar qual formaction
link é gerado para o elemento correspondente.

Atributos AnchorTagHelper com suporte para controlar o valor de formaction :

Atributo Descrição

asp-controller O nome do controlador.

asp-action O nome do método da ação.

asp-area O nome da área.

asp-page O nome da Razor página.

asp-page-handler O nome do Razor manipulador de páginas.

asp-route O nome da rota.

asp-route-{value} Um único valor de rota de URL. Por exemplo, asp-route-id="1234" .

asp-all-route-data Todos os valores de rota.

asp-fragment O fragmento de URL.

Exemplo de Enviar ao controlador


A marcação a seguir envia o formulário à ação Index de HomeController quando a
entrada ou botão são escolhidos:

CSHTML

<form method="post">
<button asp-controller="Home" asp-action="Index">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-controller="Home"
asp-action="Index">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home">
</form>

Exemplo de Enviar a uma página


A marcação a seguir envia o formulário para a About Razor Página:

CSHTML

<form method="post">
<button asp-page="About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-page="About">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/About">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/About">
</form>

Exemplo de Enviar a uma rota


Considere o ponto de extremidade /Home/Test :

C#

public class HomeController : Controller


{
[Route("/Home/Test", Name = "Custom")]
public string Test()
{
return "This is the test page";
}
}

A marcação a seguir envia o formulário ao ponto de extremidade /Home/Test .

CSHTML

<form method="post">
<button asp-route="Custom">Click Me</button>
<input type="image" src="..." alt="Or Click Me" asp-route="Custom">
</form>

A marcação anterior gera o seguinte HTML:

HTML

<form method="post">
<button formaction="/Home/Test">Click Me</button>
<input type="image" src="..." alt="Or Click Me" formaction="/Home/Test">
</form>

O auxiliar de marca de entrada


O Auxiliar de Marca de Entrada associa um elemento de entrada> HTML< a uma
expressão de modelo em seu modo de exibição razor.

Sintaxe:

CSHTML

<input asp-for="<Expression Name>">

O auxiliar de marca de entrada:

Gera os atributos HTML id e name para o nome da expressão especificada no


atributo asp-for . asp-for="Property1.Property2" é equivalente a m =>
m.Property1.Property2 . O nome da expressão é o que é usado para o valor do

atributo asp-for . Consulte a seção Nomes de expressão para obter informações


adicionais.

Define o valor do atributo HTML type com base no tipo de modelo e atributos de
anotação de dados aplicados à propriedade do modelo

O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados
às propriedades do modelo

Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e


Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar de
marca de entrada para obter detalhes.

Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to


process
this request. Please review the following specific error details and
modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type
'RegisterViewModel'
could be found (are you missing a using directive or an assembly
reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A tabela
a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados todos
os tipos .NET).

Tipo .NET Tipo de entrada

Bool type="checkbox"

String type="text"

Datetime type="datetime-local"

Byte type="number"

int type="number"

Single e Double type="number"

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos
os atributos de validação):

Atributo Tipo de entrada


Atributo Tipo de entrada

[EmailAddress] type="email"

[Url] type="url"

[HiddenInput] type="hidden"

[Phone] type="tel"

[DataType(DataType.Password)] type="password"

[DataType(DataType.Date)] type="date"

[DataType(DataType.Time)] type="time"

Exemplo:

C#

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; }
}
}

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

HTML
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email
address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value=""><br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password"><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de Marca de Entrada consome os metadados do modelo e
produz atributos HTML5 data-val-* (consulte Validação de Modelo). Esses atributos

descrevem os validadores a serem anexados aos campos de entrada. Isso fornece


validação de jQuery e HTML5 discreto. Os atributos não 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 do data-val-rule atributo.
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" .

Ao associar vários input controles à mesma propriedade, os controles gerados


compartilham o mesmo id , o que torna a marcação gerada inválida. Para evitar
duplicatas, especifique o id atributo para cada controle explicitamente.

Renderização de entrada oculta da caixa de seleção


As caixas de seleção em HTML5 não enviam um valor quando estão desmarcadas. Para
permitir que um valor padrão seja enviado para uma caixa de seleção desmarcada, o
Auxiliar de Marca de Entrada gera uma entrada oculta adicional para caixas de seleção.

Por exemplo, considere a marcação a seguir Razor que usa o Auxiliar de Marca de
Entrada para uma propriedade IsChecked de modelo booliano:

CSHTML

<form method="post">
<input asp-for="@Model.IsChecked" />
<button type="submit">Submit</button>
</form>

A marcação anterior Razor gera marcação HTML semelhante à seguinte:

HTML

<form method="post">
<input name="IsChecked" type="checkbox" value="true" />
<button type="submit">Submit</button>

<input name="IsChecked" type="hidden" value="false" />


</form>

A marcação HTML anterior mostra uma entrada oculta adicional com um nome
IsChecked e um valor de false . Por padrão, essa entrada oculta é renderizada no final

do formulário. Quando o formulário é enviado:

Se a entrada da IsChecked caixa de seleção for marcada, ambas true serão false
enviadas como valores.
Se a entrada da IsChecked caixa de seleção estiver desmarcada, somente o valor
false de entrada oculto será enviado.

O ASP.NET Core processo de associação de modelo lê apenas o primeiro valor ao


associar a um bool valor, o que resulta em true caixas de seleção marcadas e false
em caixas de seleção desmarcadas.

Para configurar o comportamento da renderização de entrada oculta, defina a


CheckBoxHiddenInputRenderMode propriedade em
MvcViewOptions.HtmlHelperOptions. Por exemplo:

C#

services.Configure<MvcViewOptions>(options =>
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode =
CheckBoxHiddenInputRenderMode.None);

O código anterior desabilita a renderização de entrada oculta para caixas de seleção


definindo CheckBoxHiddenInputRenderMode como
CheckBoxHiddenInputRenderMode.None. Para todos os modos de renderização
disponíveis, consulte a CheckBoxHiddenInputRenderMode enumeração.

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 Html.EditorFor de Marca de Entrada
e Html.TextBoxFor são fortemente tipado (eles usam expressões lambda); Html.TextBox
e Html.Editor não são (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() .

CSHTML

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de m. :

CSHTML

@{
var joe = "Joe";
}

<input asp-for="@joe">

Gera o seguinte:

HTML

<input type="text" id="joe" name="joe" value="Joe">


Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo
nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .

Quando o ASP.NET Core MVC calcula o valor de ModelExpression , ele inspeciona várias
fontes, inclusive o ModelState . Considere o <input type="text" asp-for="Name"> . O
atributo value calculado é o primeiro valor não nulo:

Da entrada de ModelState com a chave "Name".


Do resultado da expressão Model.Name .

Navegando para propriedades filho


Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém
uma propriedade Address filho.

C#

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}

C#

public class RegisterAddressViewModel


{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

CSHTML

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

HTML

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1"


value="">

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

C#

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

C#

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O seguinte Razor mostra como você acessa um elemento específico Color :

CSHTML

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>

O Views/Shared/EditorTemplates/String.cshtml modelo:

CSHTML

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

C#

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O seguinte Razor mostra como iterar em uma coleção:

CSHTML

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

</table>
<button type="submit">Save</button>
</form>

O Views/Shared/EditorTemplates/ToDoItem.cshtml modelo:

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.

7 Observação

O código de exemplo comentado acima mostra como você substituiria a expressão


lambda pelo operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O Textarea Tag Helper auxiliar de marca é semelhante ao Auxiliar de Marca de Entrada.

Gera os atributos e name os id atributos de validação de dados do modelo para


um <elemento textarea> .

Fornece tipagem forte.

Alternativa de Auxiliar HTML: Html.TextAreaFor


Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

CSHTML

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:

HTML

<form method="post" action="/Demo/RegisterTextArea">


<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type
with a maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type
with a minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

O auxiliar de marca de rótulo


Gera a legenda de rótulo e for o atributo em um <elemento de rótulo> para
um nome de expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

Ele Label Tag Helper fornece os seguintes benefícios em 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.

Exemplo:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

CSHTML

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :

HTML

<label for="Email">Email Address</label>


O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID
associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste
exemplo é proveniente do atributo Display . Se o modelo não contivesse um atributo
Display , a legenda seria o nome da propriedade da expressão. Para substituir a legenda

padrão, adicione uma legenda dentro da marca de rótulo.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O Input

Tag Helper adiciona atributos de validação do lado do cliente HTML5 para elementos de
entrada baseados em atributos de anotação de dados em suas classes de modelo. A
validação também é executada no servidor. O Auxiliar de marca de validação exibe essas
mensagens de erro quando ocorre um erro de validação.

O Auxiliar de marca de mensagem de validação


Adiciona o atributo HTML5 data-valmsg-for="property" ao elemento span ,
que anexa as mensagens de erro de validação no campo de entrada da
propriedade de modelo especificada. Quando ocorre um erro de validação do lado
do cliente, jQuery exibe a mensagem de erro no elemento <span> .

A validação também é feita no servidor. Os clientes poderão ter o JavaScript


desabilitado e parte da validação só pode ser feita no lado do servidor.

Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o asp-validation-for atributo em um


elemento html span .

CSHTML

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

HTML

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper auxiliar de marca Input
posterior para a mesma propriedade. Fazer isso exibe as mensagens de erro de
validação próximo à entrada que causou o erro.

7 Observação

É necessário ter uma exibição com as referências de script jQuery e JavaScript


corretas em vigor para a validação do lado do cliente. Consulte a 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> .

HTML

<span class="field-validation-error" data-valmsg-for="Email"


data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

asp-validation-summary Mensagens de validação exibidas

All Nível da propriedade e do modelo

ModelOnly Modelar

None Não

Amostra
No exemplo a seguir, o modelo de dados tem DataAnnotation atributos, o que gera
mensagens de erro de validação no <input> elemento. Quando ocorre um erro de
validação, o Auxiliar de marca de validação exibe a mensagem de erro:

C#

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; }
}
}

CSHTML

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):

HTML

<form action="/DemoReg/Register" method="post">


Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do
modelo.

Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

Especifica Select Tag Helper asp-for o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos de opção . Por exemplo:

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Exemplo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

C#
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

O método Index HTTP POST exibe a seleção:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg });
}

// If we got this far, something failed; redisplay form.


return View(model);
}

A exibição Index :

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="Index" method="post">


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

HTML

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

7 Observação

Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de


Seleção. Um modelo de exibição é mais robusto para fornecer metadados MVC e,
geralmente, menos problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

CSHTML

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .

Exemplo:

C#

public class CountryEnumViewModel


{
public CountryEnum EnumCountry { get; set; }
}
}

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

CSHTML

@model CountryEnumViewModel

<form asp-controller="Home" asp-action="IndexEnum" method="post">


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>

Você pode marcar sua lista de enumeradores com o Display atributo para obter uma
interface do usuário mais rica:

C#

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:

HTML

<form method="post" action="/Home/IndexEnum">


<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="
<removed for brevity>">
</form>

Grupo de opções
O elemento de grupo de opções> HTML< é gerado quando o modelo de exibição
contém um ou mais SelectListGroup objetos.

O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do


Norte" e "Europa":

C#

public class CountryViewModelGroup


{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America"
};
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:

O HTML gerado:

HTML

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo múltiplo =
"múltiplo" se a propriedade especificada no asp-for atributo for um IEnumerable . Por
exemplo, considerando o seguinte modelo:

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new


List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}

Com a seguinte exibição:

CSHTML

@model CountryViewModelIEnumerable

<form asp-controller="Home" asp-action="IndexMultiSelect" method="post">


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

HTML

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>">
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="IndexEmpty" method="post">


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O Views/Shared/EditorTemplates/CountryViewModel.cshtml modelo:

CSHTML

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>
Adicionar elementos de opção> HTML< não se limita ao caso Sem seleção. Por
exemplo, o seguinte método de ação e exibição gerarão HTML semelhante ao código
acima:

C#

public IActionResult IndexNone()


{
var model = new CountryViewModel();
model.Insert(0, new SelectListItem("<none>", ""));
return View(model);
}

CSHTML

@model CountryViewModel

<form asp-controller="Home" asp-action="IndexEmpty" method="post">


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

C#

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}

HTML

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed
for brevity>">
</form>

Recursos adicionais
Auxiliares de Marca 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
Compartilhar controladores, exibições,
Razor páginas e muito mais com As
Partes do Aplicativo
Artigo • 04/01/2023 • 8 minutos para o fim da leitura

De Rick Anderson

Exibir ou baixar código de exemplo (como baixar)

Uma Parte do Aplicativo é uma abstração sobre os recursos de um aplicativo. As Partes


de Aplicativo permitem ASP.NET Core descobrir controladores, exibir componentes,
auxiliares de marca, Razor páginas, fontes de compilação razor e muito mais.
AssemblyPart é uma parte do aplicativo. AssemblyPart encapsula uma referência de
assembly e expõe tipos e referências de compilação.

Os provedores de recursos trabalham com partes de aplicativo para preencher os


recursos de um aplicativo ASP.NET Core. O principal caso de uso para partes do
aplicativo é configurar um aplicativo para descobrir (ou evitar o carregamento) ASP.NET
Core recursos de um assembly. Por exemplo, talvez você queira compartilhar a
funcionalidade comum entre vários aplicativos. Usando As Partes de Aplicativo, você
pode compartilhar um assembly (DLL) contendo controladores, exibições, Razor
páginas, fontes de compilação razor, auxiliares de marca e muito mais com vários
aplicativos. É preferível compartilhar um assembly para duplicar código em vários
projetos.

ASP.NET Core aplicativos carregam recursos de ApplicationPart. A AssemblyPart classe


representa uma parte do aplicativo que é apoiada por um assembly.

Carregar recursos ASP.NET Core


Use as Microsoft.AspNetCore.Mvc.ApplicationParts classes e AssemblyPart para
descobrir e carregar ASP.NET Core recursos (controladores, componentes de exibição
etc.). As ApplicationPartManager partes do aplicativo e os provedores de recursos
disponíveis. ApplicationPartManager está configurado em Startup.ConfigureServices :

C#

// Requires using System.Reflection;


public void ConfigureServices(IServiceCollection services)
{
var assembly = typeof(MySharedController).Assembly;
services.AddControllersWithViews()
.AddApplicationPart(assembly)
.AddRazorRuntimeCompilation();

services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
{ options.FileProviders.Add(new EmbeddedFileProvider(assembly)); });
}

O código a seguir fornece uma abordagem alternativa para configurar


ApplicationPartManager usando AssemblyPart :

C#

// Requires using System.Reflection;


// Requires using Microsoft.AspNetCore.Mvc.ApplicationParts;
public void ConfigureServices(IServiceCollection services)
{
var assembly = typeof(MySharedController).GetTypeInfo().Assembly;
// This creates an AssemblyPart, but does not create any related parts
for items such as views.
var part = new AssemblyPart(assembly);
services.AddControllersWithViews()
.ConfigureApplicationPartManager(apm =>
apm.ApplicationParts.Add(part));
}

Os dois exemplos de código anteriores carregam o SharedController de um assembly.


Não SharedController está no projeto do aplicativo. Consulte o download de exemplo
da solução WebAppParts .

Incluir exibições
Use uma Razor biblioteca de classes para incluir exibições no assembly.

Impedir o carregamento de recursos


As partes do aplicativo podem ser usadas para evitar o carregamento de recursos em
um assembly ou local específico. Adicione ou remova membros da
Microsoft.AspNetCore.Mvc.ApplicationParts coleção para ocultar ou disponibilizar
recursos. A ordem das entradas na coleção ApplicationParts não é importante.
Configure o ApplicationPartManager antes de usá-lo para configurar os serviços no
contêiner. Por exemplo, configure o ApplicationPartManager antes de invocar
AddControllersAsServices . Chame Remove a ApplicationParts coleção para remover um
recurso.
As ApplicationPartManager partes incluem para:

Assembly do aplicativo e assemblies dependentes.


Microsoft.AspNetCore.Mvc.ApplicationParts.CompiledRazorAssemblyPart

Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
Microsoft.AspNetCore.Mvc.TagHelpers .

Microsoft.AspNetCore.Mvc.Razor .

Provedores de recursos
Os provedores de recursos de aplicativo examinam as partes do aplicativo e fornecem
recursos para essas partes. Há provedores de recursos internos para os seguintes
recursos de ASP.NET Core:

ControllerFeatureProvider
TagHelperFeatureProvider
MetadataReferenceFeatureProvider
ViewsFeatureProvider
internal class RazorCompiledItemFeatureProvider

Provedores de recursos herdam de IApplicationFeatureProvider<TFeature>, em que T é


o tipo do recurso. Os provedores de recursos podem ser implementados para qualquer
um dos tipos de recursos listados anteriormente. A ordem dos provedores de recursos
no comportamento de tempo de execução ApplicationPartManager.FeatureProviders
pode afetar. Posteriormente, os provedores adicionados podem reagir às ações
executadas por provedores adicionados anteriormente.

Exibir recursos disponíveis


Os recursos disponíveis para um aplicativo podem ser enumerados solicitando uma
injeção ApplicationPartManager por meio de dependência:

C#

using AppPartsSample.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewComponents;

namespace AppPartsSample.Controllers
{
public class FeaturesController : Controller
{
private readonly ApplicationPartManager _partManager;

public FeaturesController(ApplicationPartManager partManager)


{
_partManager = partManager;
}

public IActionResult Index()


{
var viewModel = new FeaturesViewModel();

var controllerFeature = new ControllerFeature();


_partManager.PopulateFeature(controllerFeature);
viewModel.Controllers = controllerFeature.Controllers.ToList();

var tagHelperFeature = new TagHelperFeature();


_partManager.PopulateFeature(tagHelperFeature);
viewModel.TagHelpers = tagHelperFeature.TagHelpers.ToList();

var viewComponentFeature = new ViewComponentFeature();


_partManager.PopulateFeature(viewComponentFeature);
viewModel.ViewComponents =
viewComponentFeature.ViewComponents.ToList();

return View(viewModel);
}
}
}

O exemplo de download usa o código anterior para exibir os recursos do aplicativo:

text

Controllers:
- FeaturesController
- HomeController
- HelloController
- GenericController`1
- GenericController`1
Tag Helpers:
- PrerenderTagHelper
- AnchorTagHelper
- CacheTagHelper
- DistributedCacheTagHelper
- EnvironmentTagHelper
- Additional Tag Helpers omitted for brevity.
View Components:
- SampleViewComponent
Descoberta em partes do aplicativo
Erros HTTP 404 não são incomuns ao desenvolver com partes do aplicativo. Esses erros
geralmente são causados por falta de um requisito essencial para como as partes de
aplicativos são descobertas. Se o aplicativo retornar um erro HTTP 404, verifique se os
seguintes requisitos foram atendidos:

A applicationName configuração precisa ser definida como o assembly raiz usado


para descoberta. O assembly raiz usado para descoberta normalmente é o
assembly do ponto de entrada.
O assembly raiz precisa ter uma referência às partes usadas para descoberta. A
referência pode ser direta ou transitiva.
O assembly raiz precisa fazer referência ao SDK da Web. A estrutura tem uma
lógica que carimba atributos no assembly raiz que são usados para descoberta.
Trabalhar com o modelo de aplicativo
no ASP.NET Core
Artigo • 04/01/2023 • 11 minutos para o fim da leitura

Por Steve Smith

O ASP.NET Core MVC define um modelo de aplicativo que representa os componentes


de um aplicativo MVC. Leia e manipule esse modelo para modificar como os elementos
MVC se comportam. Por padrão, o MVC segue determinadas convenções para
determinar quais classes são consideradas controladores, quais métodos nessas classes
são ações e como os parâmetros e o roteamento se comportam. Personalize esse
comportamento para atender às necessidades de um aplicativo criando convenções
personalizadas e aplicando-as globalmente ou como atributos.

Modelos e provedores
( IApplicationModelProvider )
O modelo de aplicativo ASP.NET Core MVC inclui interfaces abstratas e classes de
implementação concretas 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. Ao trabalhar com o modelo de
aplicativo, modifique um aplicativo para seguir diferentes convenções 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. Usar propriedades é uma ótima maneira de
configurar filtros, associadores de modelo e outros aspectos do modelo de aplicativo
por ação.

7 Observação

A ActionDescriptor.Properties coleção não é thread safe (para gravações) após a


inicialização do aplicativo. Convenções são a melhor maneira de adicionar dados
com segurança a essa coleção.

ASP.NET Core MVC carrega o modelo de aplicativo usando um padrão de provedor,


definido pela IApplicationModelProvider interface. Esta seção aborda alguns dos
detalhes de implementação interna de como funciona esse provedor. O uso do padrão
de provedor é um assunto avançado, principalmente para uso de estrutura. A maioria
dos aplicativos deve usar convenções, não o padrão do provedor.

Implementações da IApplicationModelProvider interface "encapsulam" umas às outras,


em que cada implementação chama OnProvidersExecuting em ordem crescente com
base em sua Order propriedade. 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

7 Observação

A ordem na qual dois provedores com o mesmo valor Order são chamados é
indefinida e não deve ser confiada.

7 Observação

IApplicationModelProvider é um conceito avançado a ser estendido pelos autores


da estrutura. Em geral, os aplicativos devem usar convenções e as 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 por construir as
ControllerModelinstâncias, que por sua vez são referências ActionModelPropertyModele
ParameterModel instâncias. A DefaultApplicationModelProvider classe é um detalhe de
implementação da estrutura interna que pode mudar no futuro.

O AuthorizationApplicationModelProvider é responsável por aplicar o comportamento


associado aos atributos AuthorizeFilter e AllowAnonymousFilter. Para obter mais
informações, consulte a autorização simples no ASP.NET Core.

O CorsApplicationModelProvider comportamento implementa associado


IEnableCorsAttribute e IDisableCorsAttribute. Para obter mais informações, consulte
Habilitar SOLICITAções de Origem Cruzada (CORS) em ASP.NET Core.

As informações sobre os provedores internos da estrutura descritas nesta seção não


estão disponíveis por meio do navegador de API .NET. No entanto, os provedores
podem ser inspecionados na fonte de referência ASP.NET Core (repositório GitHub do
dotnet/aspnetcore) . Use a pesquisa do GitHub para localizar os provedores pelo
nome e selecione a versão da origem com a lista suspensa de branches/marcas do
Switch .

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 de modificar o
comportamento de um aplicativo. As convenções fornecem uma maneira de escrever
código que aplica personalizações dinamicamente. Embora os filtros forneçam um meio
de modificar o comportamento da estrutura, as personalizações permitem o controle
sobre como todo o aplicativo funciona em conjunto.

As seguintes convenções estão disponíveis:


IApplicationModelConvention
IControllerModelConvention
IActionModelConvention
IParameterModelConvention

As convenções são aplicadas adicionando-as às opções de MVC ou implementando


atributos e aplicando-os a controladores, ações ou parâmetros de ação (semelhante a
filtros). Ao contrário dos filtros, as convenções só são executadas quando o aplicativo
está sendo iniciado, não como parte de cada solicitação.

7 Observação

Para obter informações sobre Razor as convenções de provedor de modelo de


aplicativo e de rota do Pages, consulte Razor as convenções de rota e de
aplicativo do Pages em ASP.NET Core.

Modificar o ApplicationModel
A convenção a seguir é usada para adicionar uma propriedade ao modelo de aplicativo:

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ApplicationDescription : IApplicationModelConvention
{
private readonly string _description;

public ApplicationDescription(string description)


{
_description = description;
}

public void Apply(ApplicationModel application)


{
application.Properties["description"] = _description;
}
}
}

As convenções de modelo de aplicativo são aplicadas como opções quando o MVC é


adicionado em Startup.ConfigureServices :
C#

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc(options =>
{
options.Conventions.Add(new ApplicationDescription("My Application
Description"));
options.Conventions.Add(new NamespaceRoutingConvention());
});
}

As propriedades são acessíveis da coleção dentro das ActionDescriptor.Properties ações


do controlador:

C#

public class AppModelController : Controller


{
public string Description()
{
return "Description: " +
ControllerContext.ActionDescriptor.Properties["description"];
}
}

Modificar a ControllerModel descrição


O modelo de controlador também pode incluir propriedades personalizadas. As
propriedades personalizadas substituem as propriedades existentes com o mesmo
nome especificado no modelo de aplicativo. O seguinte atributo de convenção adiciona
uma descrição no nível do controlador:

C#

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ControllerDescriptionAttribute : Attribute,
IControllerModelConvention
{
private readonly string _description;

public ControllerDescriptionAttribute(string description)


{
_description = description;
}

public void Apply(ControllerModel controllerModel)


{
controllerModel.Properties["description"] = _description;
}
}
}

Essa convenção é aplicada como um atributo em um controlador:

C#

[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " +
ControllerContext.ActionDescriptor.Properties["description"];
}

Modificar a ActionModel descrição


Uma convenção de atributo separada pode ser aplicada a ações individuais,
substituindo o comportamento já aplicado no nível do aplicativo ou controlador:

C#

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ActionDescriptionAttribute : Attribute,
IActionModelConvention
{
private readonly string _description;

public ActionDescriptionAttribute(string description)


{
_description = description;
}

public void Apply(ActionModel actionModel)


{
actionModel.Properties["description"] = _description;
}
}
}
Aplicar isso a uma ação dentro do controlador demonstra como ele substitui a
convenção no nível do controlador:

C#

[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"];
}
}

Modificar o ParameterModel
A convenção a seguir pode ser aplicada a parâmetros de ação para modificar seu
BindingInfo. A convenção a seguir exige que o parâmetro seja um parâmetro de rota.
Outras fontes de associação potenciais, como valores de cadeia de caracteres de
consulta, são ignoradas:

C#

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace AppModelSample.Conventions
{
public class MustBeInRouteParameterModelConvention : Attribute,
IParameterModelConvention
{
public void Apply(ParameterModel model)
{
if (model.BindingInfo == null)
{
model.BindingInfo = new BindingInfo();
}
model.BindingInfo.BindingSource = BindingSource.Path;
}
}
}

O atributo pode ser aplicado a qualquer parâmetro de ação:

C#

public class ParameterModelController : Controller


{
// Will bind: /ParameterModel/GetById/123
// WON'T bind: /ParameterModel/GetById?id=123
public string GetById([MustBeInRouteParameterModelConvention]int id)
{
return $"Bound to id: {id}";
}
}

Para aplicar a convenção a todos os parâmetros de ação, adicione-o


MustBeInRouteParameterModelConvention aMvcOptions: Startup.ConfigureServices

C#

options.Conventions.Add(new MustBeInRouteParameterModelConvention());

Modificar o ActionModel nome


A convenção a seguir modifica o ActionModel para atualizar o nome da ação ao qual ele
é aplicado. O novo nome é fornecido como um parâmetro para o atributo. Esse novo
nome é usado pelo roteamento, portanto, afeta a rota usada para chegar a este método
de ação:

C#

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class CustomActionNameAttribute : Attribute,
IActionModelConvention
{
private readonly string _actionName;

public CustomActionNameAttribute(string actionName)


{
_actionName = actionName;
}
public void Apply(ActionModel actionModel)
{
// this name will be used by routing
actionModel.ActionName = _actionName;
}
}
}

Esse atributo é aplicado a um método de ação no HomeController :

C#

// 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 .

7 Observação

Este exemplo nesta seção é essencialmente o mesmo que usar o interno


ActionNameAttribute.

Convenção de roteamento personalizado


Use um IApplicationModelConvention para personalizar como o roteamento funciona.
Por exemplo, a convenção a seguir incorpora namespaces de controladores em suas
rotas, substituindo . no namespace / na rota:

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;

namespace AppModelSample.Conventions
{
public class NamespaceRoutingConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var hasAttributeRouteModels = controller.Selectors
.Any(selector => selector.AttributeRouteModel != null);

if (!hasAttributeRouteModels
&& controller.ControllerName.Contains("Namespace")) //
affect one controller in this sample
{
// Replace the . in the namespace with a / to create the
attribute route
// Ex: MySite.Admin namespace will correspond to
MySite/Admin attribute route
// Then attach [controller], [action] and optional {id?}
token.
// [Controller] and [action] is replaced with the
controller and action
// name to generate the final template
controller.Selectors[0].AttributeRouteModel = new
AttributeRouteModel()
{
Template =
controller.ControllerType.Namespace.Replace('.', '/') +
"/[controller]/[action]/{id?}"
};
}
}

// You can continue to put attribute route templates for the


controller actions depending on the way you want them to behave
}
}
}

A convenção é adicionada como uma opção em Startup.ConfigureServices :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc(options =>
{
options.Conventions.Add(new ApplicationDescription("My Application
Description"));
options.Conventions.Add(new NamespaceRoutingConvention());
});
}

 Dica
Adicione convenções ao middlewareMvcOptions usando a abordagem a seguir. O
{CONVENTION} espaço reservado é a convenção a ser adicionada:

C#

services.Configure<MvcOptions>(c => c.Conventions.Add({CONVENTION}));

O exemplo a seguir aplica uma convenção a rotas que não estão usando o roteamento
de atributo em que o controlador tem Namespace em seu nome:

C#

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.";
}
}
}

Usar ApiExplorer para documentar um


aplicativo
O modelo de aplicativo expõe uma propriedade ApiExplorerModel em cada nível, que
pode ser usada para percorrer a estrutura do aplicativo. Isso pode ser usado para gerar
páginas de ajuda para APIs web usando ferramentas como o Swagger. A ApiExplorer
propriedade expõe uma IsVisible propriedade que pode ser definida para especificar
quais partes do modelo do aplicativo devem ser expostas. Defina essa configuração
usando uma convenção:

C#

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), a visibilidade da API


está habilitada ou desabilitada em qualquer nível dentro de um aplicativo.
Áreas no ASP.NET Core
Artigo • 04/01/2023 • 24 minutos para o fim da leitura

Por Dhananjay Kumar e Rick Anderson

As áreas são um recurso ASP.NET usado para organizar a funcionalidade relacionada em


um grupo como um grupo separado:

Namespace para roteamento.


Estrutura de pastas para exibições e Razor páginas.

O uso de áreas cria uma hierarquia para fins de roteamento adicionando outro
parâmetro de rota, area para controller e action ou para uma Razor página page .

As áreas fornecem uma maneira de particionar um aplicativo Web ASP.NET Core em


grupos funcionais menores, cada um com seu próprio conjunto de Razor Páginas,
controladores, exibições e modelos. Uma área é efetivamente uma estrutura MVC
dentro de um aplicativo. Em um projeto Web do ASP.NET Core, os componentes lógicos
como páginas, modelo, controlador e modo de exibição são mantidos em pastas
diferentes. O runtime do ASP.NET Core usa as 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. Cada uma dessas unidades tem sua própria área para
conter exibições, controladores, Razor páginas e modelos.

Considere o uso de Áreas em um projeto quando:

O aplicativo é composto por vários componentes funcionais de alto nível que


podem ser separados logicamente.
Você deseja particionar o aplicativo para que cada área funcional possa ser
trabalhada de forma independente.

Se você estiver usando Razor Páginas, consulte Áreas com Razor Páginas neste
documento.

Áreas para os controladores com modos de


exibição
Um aplicativo Web do ASP.NET Core típico usando áreas, controladores e exibições
contém o seguinte:
Uma estrutura de pastas da área.

Controladores com o [Area] atributo para associar o controlador à área:

C#

[Area("Products")]
public class ManageController : Controller
{

A rota de área adicionada a Program.cs:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "MyArea",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Estrutura de pastas da área


Considere um aplicativo que tem dois grupos lógicos, Produtos e Serviços. Usando áreas,
a estrutura de pastas seria semelhante ao seguinte:

Nome do projeto
Áreas
Produtos
Controladores
HomeController.cs
ManageController.cs
Exibições
Home
Index.cshtml
Gerenciar
Index.cshtml
About.cshtml
Serviços
Controladores
HomeController.cs
Exibições
Home
Index.cshtml

Enquanto o layout anterior é típico ao usar áreas, somente os arquivos de exibição são
necessários para usar essa estrutura de pastas. Pesquisas de descoberta de exibição para
um arquivo de exibição de área correspondente, na seguinte ordem:

text

/Areas/<Area-Name>/Views/<Controller-Name>/<Action-Name>.cshtml
/Areas/<Area-Name>/Views/Shared/<Action-Name>.cshtml
/Views/Shared/<Action-Name>.cshtml
/Pages/Shared/<Action-Name>.cshtml

Associar o controlador a uma área


Os controladores de área são designados com o [Area] atributo:

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.Docs.Samples;

namespace MVCareas.Areas.Products.Controllers;

[Area("Products")]
public class ManageController : Controller
{
public IActionResult Index()
{
ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
return View();
}

public IActionResult About()


{
ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
return View();
}
}

Adicionar rota de área


As rotas de área normalmente usam roteamento convencional em vez de roteamento
de atributo. 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.

{area:...} pode ser usado como um token em modelos de rota, se o espaço de URL é

uniforme entre todas as áreas:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "MyArea",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();

No código anterior, exists aplica uma restrição de que a rota deve corresponder a uma
área. Usando {area:...} com MapControllerRoute :

É o mecanismo menos complicado para adicionar roteamento a áreas.


Corresponde a todos os controladores com o [Area("Area name")] atributo.

O código a seguir usa MapAreaControllerRoute para criar duas rotas de área nomeadas:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute(
name: "MyAreaProducts",
areaName: "Products",
pattern: "Products/{controller=Home}/{action=Index}/{id?}");

app.MapAreaControllerRoute(
name: "MyAreaServices",
areaName: "Services",
pattern: "Services/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Para obter mais informações, veja Roteamento de área.


Geração de links com áreas do MVC
O código a seguir do download do exemplo mostra geração de link com a área
especificada:

CSHTML

<li>Anchor Tag Helper links</li>


<ul>
<li>
<a asp-area="Products" asp-controller="Home" asp-action="About">
Products/Home/About
</a>
</li>
<li>
<a asp-area="Services" asp-controller="Home" asp-action="About">
Services About
</a>
</li>
<li>
<a asp-area="" asp-controller="Home" asp-action="About">
/Home/About
</a>
</li>
</ul>
<li>Html.ActionLink generated links</li>
<ul>
<li>
@Html.ActionLink("Product/Manage/About", "About", "Manage",
new { area = "Products" })
</li>
</ul>
<li>Url.Action generated links</li>
<ul>
<li>
<a href='@Url.Action("About", "Manage", new { area = "Products" })'>
Products/Manage/About
</a>
</li>
</ul>

O download de exemplo inclui uma exibição parcial que contém:

Os links anteriores.
Links semelhantes aos anteriores, exceto que area não são especificados.

A exibição parcial é referenciada no arquivo de layout, portanto, todas as páginas no


aplicativo exibem os links gerados. Os links gerados sem especificar a área só são
válidos quando referenciados de uma página na mesma área e no mesmo controlador.
Quando a área ou o controlador não for especificado, o roteamento dependerá dos
valores do ambiente. Os valores de rota atuais da solicitação atual são considerados
valores de ambiente para a geração de link. Em muitos casos para o aplicativo de
exemplo, usar os valores de ambiente gera links incorretos com a marcação que não
especifica a área.

Para obter mais informações, veja Roteamento para ações do controlador.

Layout compartilhado para áreas usando o arquivo


_ViewStart.cshtml
Para compartilhar um layout comum para todo o aplicativo, mantenha o
_ViewStart.cshtml na pasta raiz do aplicativo. Para obter mais informações, consulte
Layout no ASP.NET Core

Pasta raiz do aplicativo


A pasta raiz do aplicativo é a pasta que contém o Program.cs arquivo em um aplicativo
Web criado com os modelos ASP.NET Core.

_ViewImports.cshtml
/Views/_ViewImports.cshtml, para MVC e /Pages/_ViewImports.cshtml for Razor Pages,
não é importado para exibições em áreas. Use uma das seguintes abordagens para
fornecer importações de exibição para todos os modos de exibição:

Adicione _ViewImports.cshtml à pasta raiz do aplicativo. Um _ViewImports.cshtml


na pasta raiz do aplicativo será aplicado a todos os modos de exibição no
aplicativo.
Copie o arquivo _ViewImports.cshtml para a pasta de exibição apropriada em áreas.
Por exemplo, um Razor aplicativo Pages criado com contas de usuário individuais
tem um arquivo _ViewImports.cshtml nas seguintes pastas:
/Areas/Identity/Pages/_ViewImports.cshtml
/Pages/_ViewImports.cshtml

O arquivo _ViewImports.cshtml normalmente contém importações de @using Auxiliares


de Marca e @inject instruções. Para obter mais informações, consulte Importar Diretivas
Compartilhadas.
Alterar a pasta de área padrão em que as exibições são
armazenadas
O código a seguir altera a pasta da área padrão de "Areas" para "MyAreas" :

C#

using Microsoft.AspNetCore.Mvc.Razor;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<RazorViewEngineOptions>(options =>
{
options.AreaViewLocationFormats.Clear();

options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/{1}/{0}.cshtml");

options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/Shared/{0}.cshtml");
options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
});

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "MyArea",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Áreas com Razor Páginas


Áreas com Razor Páginas exigem uma Areas/<area name>/Pages pasta na raiz do
aplicativo. A seguinte estrutura de pasta é usada com o aplicativo de exemplo :

Nome do projeto
Áreas
Produtos
Pages (Páginas)
_ViewImports
Sobre
Índice
Serviços
Pages (Páginas)
Gerenciar
Sobre
Índice

Vincular geração com Razor páginas e áreas


O código a seguir do download do exemplo mostra a geração de links com a área
especificada (por exemplo, asp-area="Products" ):

CSHTML

<li>Anchor Tag Helper links</li>


<ul>
<li>
<a asp-area="Products" asp-page="/About">
Products/About
</a>
</li>
<li>
<a asp-area="Services" asp-page="/Manage/About">
Services/Manage/About
</a>
</li>
<li>
<a asp-area="" asp-page="/About">
/About
</a>
</li>
</ul>
<li>Url.Page generated links</li>
<ul>
<li>
<a href='@Url.Page("/Manage/About", new { area = "Services" })'>
Services/Manage/About
</a>
</li>
<li>
<a href='@Url.Page("/About", new { area = "Products" })'>
Products/About
</a>
</li>
</ul>

O download de exemplo inclui uma exibição parcial que contém os links anteriores e os
mesmos links sem especificar a área. A exibição parcial é referenciada no arquivo de
layout, portanto, todas as páginas no aplicativo exibem os links gerados. Os links
gerados sem especificar a área só são válidos quando referenciados de uma página na
mesma área.

Quando a área não for especificada, o roteamento dependerá dos valores do ambiente.
Os valores de rota atuais da solicitação atual são considerados valores de ambiente para
a geração de link. Em muitos casos, para o aplicativo de exemplo, usar os valores de
ambiente gera links incorretos. Por exemplo, considere os links gerados a partir do
código a seguir:

CSHTML

<li>
<a asp-page="/Manage/About">
Services/Manage/About
</a>
</li>
<li>
<a asp-page="/About">
/About
</a>
</li>

Para o código anterior:

O link gerado de <a asp-page="/Manage/About"> só estará correto quando a última


solicitação tiver sido para uma página na área Services . Por exemplo,
/Services/Manage/ , /Services/Manage/Index ou /Services/Manage/About .

O link gerado a partir <a asp-page="/About"> só estará correto quando a última


solicitação tiver sido para uma página na área /Home .
O código vem do download de exemplo .

Importar o namespace e os Auxiliares de marca com o


arquivo _ViewImports
Um arquivo _ViewImports.cshtml pode ser adicionado a cada pasta Páginas de área para
importar o namespace e auxiliares de marca para cada Razor página da pasta.

Considere a área Serviços do código de exemplo, que não contém um arquivo


_ViewImports.cshtml. A seguinte marcação mostra a página
/Services/Manage/AboutRazor :

CSHTML

@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model RPareas.Areas.Services.Pages.Manage.AboutModel
@{
ViewData["Title"] = "Srv Mng About";
}

<div>
ViewData["routeInfo"]: @ViewData["routeInfo"]
</div>

<a asp-area="Products" asp-page="/Index">


Products/Index
</a>

Na marcação anterior:

O nome da classe totalmente qualificado deve ser usado para especificar o modelo
( @model RPareas.Areas.Services.Pages.Manage.AboutModel ).
Os Auxiliares de Marcação são habilitados por @addTagHelper *,
Microsoft.AspNetCore.Mvc.TagHelpers

No download de exemplo, a área Produtos contém o seguinte arquivo


_ViewImports.cshtml:

CSHTML

@namespace RPareas.Areas.Products.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

A marcação a seguir mostra a página /Products/AboutRazor :

CSHTML

@page
@model AboutModel
@{
ViewData["Title"] = "Prod About";
}
No arquivo anterior, o namespace e a diretiva @addTagHelper são importados para o
arquivo por meio do arquivo Areas/Products/Pages/_ViewImports.cshtml.

Para saber mais, confira Gerenciar o escopo do Auxiliar de Marcação e Importar diretivas
compartilhadas.

Layout compartilhado para Razor áreas de páginas


Para compartilhar um layout comum para o aplicativo inteiro, mova o _ViewStart.cshtml
para a pasta raiz do aplicativo.

Publicando áreas
Todos os arquivos *.cshtml e os arquivos do diretório wwwroot são publicados em uma
saída quando <Project Sdk="Microsoft.NET.Sdk.Web"> são incluídos no arquivo *.csproj.

Adicionar área do MVC com o Visual Studio


Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e
selecione ADICIONAR > Novo Item Scaffolded e selecione Área do MVC.

Recursos adicionais
Exibir ou baixar o código de exemplo (como baixar). O exemplo de download
fornece um aplicativo básico para áreas de teste.
MyDisplayRouteInfo e ToCtxString são fornecidos pelo pacote NuGet
Rick.Docs.Samples.RouteInfo . Os métodos exibem Controller e roteiam Razor
Page informações.
Filtros no ASP.NET Core
Artigo • 28/11/2022 • 49 minutos para o fim da leitura

Por Kirk Larkin , Rick Anderson , Tom Dykstra e Steve Smith

Os filtros no ASP.NET Core permitem que o código seja executado antes ou depois de
estágios específicos no pipeline de processamento de solicitação.

O filtros internos lidam com tarefas como:

Autorização, impedindo o acesso a recursos para os quais um usuário não está


autorizado.
Cache de resposta, curto-circuito do pipeline de solicitação para retornar uma
resposta armazenada em cache.

É possível criar filtros personalizados para lidar com interesses paralelos. Entre os
exemplos de interesses paralelos estão o tratamento de erros, cache, configuração,
autorização e registro em log. Filtros evitam a duplicação do código. Por exemplo, um
filtro de exceção de tratamento de erro poderia consolidar o tratamento de erro.

Este documento se aplica a Razor Páginas, controladores de API e controladores com


exibições. Os filtros não funcionam diretamente com Razor componentes. Um filtro só
pode afetar indiretamente um componente quando:

O componente é inserido em uma página ou exibição.


A página ou o controlador e a exibição usam o filtro.

Como os filtros funcionam


Os filtros são executados dentro do pipeline de invocação de ações do ASP.NET Core, às
vezes chamado de pipeline de filtros. O pipeline de filtro é executado após ASP.NET Core
seleciona a ação a ser executada:
Tipos de filtro
Cada tipo de filtro é executado em um estágio diferente no pipeline de filtros:

Filtros de autorização:
Execute primeiro.
Determine se o usuário está autorizado para a solicitação.
Curto-circuito do pipeline se a solicitação não estiver autorizada.

Filtros de recurso:
Execute após a autorização.
OnResourceExecuting executa o código antes do restante do pipeline de filtro.
Por exemplo, OnResourceExecuting executa o código antes da associação de
modelo.
OnResourceExecuted executa o código após a conclusão do restante do
pipeline.

Filtros de ação:
Execute imediatamente antes e depois que um método de ação for chamado.
Pode alterar os argumentos passados para uma ação.
Pode alterar o resultado retornado da ação.
Não há suporte no Razor Pages.

Filtros de ponto de extremidade:


Execute imediatamente antes e depois que um método de ação for chamado.
Pode alterar os argumentos passados para uma ação.
Pode alterar o resultado retornado da ação.
Não há suporte no Razor Pages.
Pode ser invocado em ações e pontos de extremidade baseados em
manipulador de rotas.

Os filtros de exceção aplicam políticas globais a exceções sem tratamento que


ocorrem antes que o corpo da resposta seja gravado.

Filtros de resultado:
Execute imediatamente antes e depois da execução dos resultados da ação.
Execute somente quando o método de ação for executado com êxito.
São úteis para a lógica que deve envolver a exibição ou a execução do
formatador.

O diagrama a seguir mostra como os tipos de filtro interagem no pipeline de filtro:


Razor As páginas também dão suporte Razor a filtros de página, que são executados
antes e depois de um Razor manipulador de página.

Implementação
Os filtros dão suporte a implementações síncronas e assíncronas por meio de diferentes
definições de interface.

Os filtros síncronos são executados antes e depois do estágio do pipeline. Por exemplo,
OnActionExecuting é chamado antes que o método de ação seja chamado.
OnActionExecuted é chamado depois que o método de ação retorna:

C#

public class SampleActionFilter : IActionFilter


{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
}

public void OnActionExecuted(ActionExecutedContext context)


{
// Do something after the action executes.
}
}

Os filtros assíncronos definem um On-Stage-ExecutionAsync método . Por exemplo


OnActionExecutionAsync:

C#

public class SampleAsyncActionFilter : IAsyncActionFilter


{
public async Task OnActionExecutionAsync(
ActionExecutingContext context, ActionExecutionDelegate next)
{
// Do something before the action executes.
await next();
// Do something after the action executes.
}
}

No código anterior, o SampleAsyncActionFilter tem um ActionExecutionDelegate, next ,


que executa o método de ação.
Vários estágios do filtro
É possível implementar interfaces para vários estágios do filtro em uma única classe. Por
exemplo, a ActionFilterAttribute classe implementa:

Síncrono: IActionFilter e IResultFilter


Assíncrono: IAsyncActionFilter e IAsyncResultFilter
IOrderedFilter

Implemente ou a versão assíncrona ou a versão síncrona de uma interface de filtro, não


ambas. Primeiro, o runtime 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 as
interfaces síncrona e assíncrona forem implementadas em uma classe, somente o
método assíncrono será chamado. Ao usar classes abstratas como ActionFilterAttribute,
substitua apenas os métodos síncronos ou os métodos assíncronos para cada tipo de
filtro.

Atributos de filtro internos


O ASP.NET Core inclui filtros internos baseados em atributos que podem ser
organizados em subclasses e personalizados. Por exemplo, o filtro de resultado a seguir
adiciona um cabeçalho à resposta:

C#

public class ResponseHeaderAttribute : ActionFilterAttribute


{
private readonly string _name;
private readonly string _value;

public ResponseHeaderAttribute(string name, string value) =>


(_name, _value) = (name, value);

public override void OnResultExecuting(ResultExecutingContext context)


{
context.HttpContext.Response.Headers.Add(_name, _value);

base.OnResultExecuting(context);
}
}

Os atributos permitem que os filtros aceitem argumentos, conforme mostrado no


exemplo acima. Aplique o ResponseHeaderAttribute a um controlador ou método de
ação e especifique o nome e o valor do cabeçalho HTTP:

C#
[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
public IActionResult Index() =>
Content("Examine the response headers using the F12 developer
tools.");

// ...

Use uma ferramenta como as ferramentas de desenvolvedor do navegador para


examinar os cabeçalhos. Em Cabeçalhos de Resposta, filter-header: Filter Value é
exibido.

O código a seguir se aplica ResponseHeaderAttribute a um controlador e a uma ação:

C#

[ResponseHeader("Filter-Header", "Filter Value")]


public class ResponseHeaderController : ControllerBase
{
public IActionResult Index() =>
Content("Examine the response headers using the F12 developer
tools.");

// ...

[ResponseHeader("Another-Filter-Header", "Another Filter Value")]


public IActionResult Multiple() =>
Content("Examine the response headers using the F12 developer
tools.");
}

As respostas da ação Multiple incluem os seguintes cabeçalhos:

filter-header: Filter Value


another-filter-header: Another Filter Value

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

Os filtros não podem ser aplicados aos Razor métodos de manipulador de página. Eles
podem ser aplicados ao Razor modelo de página ou globalmente.

Escopos e ordem de execução dos filtros


Um filtro pode ser adicionado ao pipeline com um de três escopos:

Usando um atributo em um controlador ou Razor Página.


Usando um atributo em uma ação do controlador. Os atributos de filtro não
podem ser aplicados aos Razor métodos do manipulador Pages.
Globalmente para todos os controladores, ações e Razor Páginas, conforme
mostrado no código a seguir:

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add<GlobalSampleActionFilter>();
});

Ordem padrão de execução


Quando há vários filtros para um determinado estágio do pipeline, o escopo determina
a ordem padrão de execução dos filtros. Filtros globais circundam filtros de classe, que,
por sua vez, circundam filtros de método.

Como resultado do aninhamento de filtro, o código posterior dos filtros é executado na


ordem inversa do código anterior. A sequência de filtro:

O código anterior dos filtros globais.


O código anterior dos filtros de controlador.
O código anterior dos filtros de método de ação.
O código posterior dos filtros de método de ação.
O código posterior dos filtros de controlador.
O código posterior dos filtros globais.

O exemplo a seguir ilustra a ordem na qual os métodos de filtro são executados para
filtros de ação síncrona:
Sequência Escopo do filtro Método do filtro

1 Global OnActionExecuting

2 Controller OnActionExecuting

3 Ação OnActionExecuting

4 Ação OnActionExecuted

5 Controller OnActionExecuted

6 Global OnActionExecuted

Filtros no nível do controlador


Cada controlador que herda de Controller inclui os OnActionExecutingmétodos ,
OnActionExecutionAsynce OnActionExecuted . Esses métodos encapsulam os filtros
executados para uma determinada ação:

OnActionExecuting é executado antes de qualquer um dos filtros da ação.

OnActionExecuted é executado após todos os filtros da ação.


OnActionExecutionAsync é executado antes de qualquer um dos filtros da ação.

Código após uma chamada a ser next executada após os filtros da ação.

A seguinte ControllerFiltersController classe:

Aplica o SampleActionFilterAttribute ( [SampleActionFilter] ) ao controlador.


Substitui OnActionExecuting e OnActionExecuted .

C#

[SampleActionFilter]
public class ControllerFiltersController : Controller
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.
{nameof(OnActionExecuting)}");

base.OnActionExecuting(context);
}

public override void OnActionExecuted(ActionExecutedContext context)


{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.
{nameof(OnActionExecuted)}");

base.OnActionExecuted(context);
}

public IActionResult Index()


{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

return Content("Check the Console.");


}
}

Navegar até https://localhost:<port>/ControllerFilters executa o seguinte código:

ControllerFiltersController.OnActionExecuting

GlobalSampleActionFilter.OnActionExecuting

SampleActionFilterAttribute.OnActionExecuting
ControllerFiltersController.Index

SampleActionFilterAttribute.OnActionExecuted
GlobalSampleActionFilter.OnActionExecuted

ControllerFiltersController.OnActionExecuted

Os filtros no nível do controlador definem a propriedade Order como int.MinValue .


Os filtros no nível do controlador não podem ser definidos para serem executados após
os filtros aplicados aos métodos. A ordem é explicada na próxima seção.

Para Razor Páginas, consulte Implementar Razor filtros de página substituindo métodos
de filtro.

Substituir a ordem padrão


É possível substituir a sequência padrão de execução implementando IOrderedFilter.
IOrderedFilter expõe a propriedade Order que tem precedência sobre o escopo para

determinar a ordem da execução. Um filtro com um valor de Order menor:

Executa o código anterior antes de um filtro com um valor mais alto de Order .
Executa o código posterior após um filtro com um valor mais alto de Order .

No exemplo de filtros de nível do controlador, tem escopo global, portanto,


GlobalSampleActionFilter ele é executado antes SampleActionFilterAttribute de , que

tem o escopo do controlador. Para executar SampleActionFilterAttribute primeiro,


defina sua ordem como int.MinValue :
C#

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
// ...
}

Para que o filtro global seja GlobalSampleActionFilter executado primeiro, defina como
Order int.MinValue :

C#

builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});

Cancelamento e curto-circuito
O pipeline de filtro pode sofrer um curto-circuito por meio da configuração da
propriedade Result no parâmetro ResourceExecutingContext fornecido ao método do
filtro. Por exemplo, o filtro de recurso a seguir impede que o restante do pipeline seja
executado:

C#

public class ShortCircuitingResourceFilterAttribute : Attribute,


IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult
{
Content = nameof(ShortCircuitingResourceFilterAttribute)
};
}

public void OnResourceExecuted(ResourceExecutedContext context) { }


}

No código a seguir, os filtros [ShortCircuitingResourceFilter] e [ResponseHeader] têm


como destino o método de ação Index . O ShortCircuitingResourceFilterAttribute
filtro:
É executado primeiro, porque ele é um filtro de recurso e ResponseHeaderAttribute
é um filtro de ação.
Causa um curto-circuito no restante do pipeline.

Portanto, o filtro ResponseHeaderAttribute nunca é executado para a ação Index . Esse


comportamento seria o mesmo se os dois filtros fossem aplicados no nível do método
de ação, desde que ShortCircuitingResourceFilterAttribute fosse executado primeiro.
O ShortCircuitingResourceFilterAttribute é executado primeiro devido ao seu tipo de
filtro:

C#

[ResponseHeader("Filter-Header", "Filter Value")]


public class ShortCircuitingController : Controller
{
[ShortCircuitingResourceFilter]
public IActionResult Index() =>
Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}

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 um tipo for adicionado, ele será
ativado pelo tipo. Um filtro ativado por tipo significa:

Uma instância é criada para cada solicitação.


Qualquer dependência de construtor é preenchida pela injeção de dependência
(DI).

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). As dependências do construtor não podem ser
fornecidas pela DI porque os atributos devem ter seus parâmetros de construtor
fornecidos onde são aplicados.

Os filtros a seguir são compatíveis com dependências de construtor fornecidas pela


injeção de dependência:

ServiceFilterAttribute
TypeFilterAttribute
IFilterFactory implementado no atributo.

Os filtros anteriores podem ser aplicados a um controlador ou a uma ação.


Os agentes estão disponíveis na DI. No entanto, evite criar e usar filtros apenas para fins
de registro em log. O registro em log da estrutura interna normalmente fornece o que é
necessário para o registro em log. Registro em log adicionado aos filtros:

Deve se concentrar em questões de domínio de negócios ou comportamento


específico ao filtro.
Não deve registrar ações ou outros eventos da estrutura. Os filtros internos já
registram ações e eventos de estrutura.

ServiceFilterAttribute
Os tipos de implementação do filtro de serviço são registrados em Program.cs . Um
ServiceFilterAttribute recupera uma instância do filtro da DI.

O código a seguir mostra a LoggingResponseHeaderFilterService classe , que usa DI:

C#

public class LoggingResponseHeaderFilterService : IResultFilter


{
private readonly ILogger _logger;

public LoggingResponseHeaderFilterService(
ILogger<LoggingResponseHeaderFilterService> logger) =>
_logger = logger;

public void OnResultExecuting(ResultExecutingContext context)


{
_logger.LogInformation(
$"- {nameof(LoggingResponseHeaderFilterService)}.
{nameof(OnResultExecuting)}");

context.HttpContext.Response.Headers.Add(
nameof(OnResultExecuting),
nameof(LoggingResponseHeaderFilterService));
}

public void OnResultExecuted(ResultExecutedContext context)


{
_logger.LogInformation(
$"- {nameof(LoggingResponseHeaderFilterService)}.
{nameof(OnResultExecuted)}");
}
}

No código a seguir, LoggingResponseHeaderFilterService é adicionado ao contêiner de


DI:
C#

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

No código a seguir, o atributo ServiceFilter recupera uma instância do filtro


LoggingResponseHeaderFilterService da DI:

C#

[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]
public IActionResult WithServiceFilter() =>
Content($"- {nameof(FilterDependenciesController)}.
{nameof(WithServiceFilter)}");

Ao usar ServiceFilterAttribute , configuração ServiceFilterAttribute.IsReusable:

Fornece uma dica de que a instância de filtro pode ser reutilizada fora do escopo
de solicitação em que foi criada. O runtime do ASP.NET Core não garante:
Que uma única instância do filtro será criada.
Que o filtro não será solicitado novamente no contêiner de DI em algum
momento posterior.
Não deve ser usado com um filtro que dependa de serviços com um tempo de
vida diferente de singleton.

ServiceFilterAttribute implementa IFilterFactory. IFilterFactory expõe o método


CreateInstance para criar uma instância de IFilterMetadata. CreateInstance carrega o
tipo especificado na DI.

TypeFilterAttribute
O TypeFilterAttribute é semelhante ao ServiceFilterAttribute, mas seu tipo não é
resolvido diretamente por meio do contêiner de DI. Ele cria uma instância do tipo
usando Microsoft.Extensions.DependencyInjection.ObjectFactory.

Como os tipos TypeFilterAttribute não são resolvidos diretamente do contêiner de DI:

Os tipos referenciados usando o TypeFilterAttribute não precisam ser


registrados no contêiner de DI. Eles têm suas dependências atendidas pelo
contêiner de DI.
Opcionalmente, o TypeFilterAttribute pode aceitar argumentos de construtor
para o tipo.

Ao usar TypeFilterAttribute , configuração TypeFilterAttribute.IsReusable:


Fornece uma dica de que a instância de filtro pode ser reutilizada fora do escopo
de solicitação em que foi criada. O runtime do ASP.NET Core não fornece garantias
de que uma única instância do filtro será criada.

Não deve ser usado com um filtro que dependa dos serviços com um tempo de
vida diferente de singleton.

O exemplo a seguir mostra como passar argumentos para um tipo usando


TypeFilterAttribute :

C#

[TypeFilter(typeof(LoggingResponseHeaderFilter),
Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
Content($"- {nameof(FilterDependenciesController)}.
{nameof(WithTypeFilter)}");

Filtros de autorização
Filtros de autorização:

São os primeiros filtros executados no pipeline de filtro.


Controlam o acesso aos métodos de ação.
Têm um método anterior, mas não têm um método posterior.

Filtros de autorização personalizados exigem uma estrutura de autorização


personalizada. Prefira configurar as políticas de autorização ou escrever uma política de
autorização personalizada em vez de escrever um filtro personalizado. O filtro de
autorização interno:

Chama o sistema de autorização.


Não autoriza solicitações.

Não gera exceções dentro de filtros de autorização:

A exceção não será tratada.


Os filtros de exceção não tratarão a exceção.

Considere a possibilidade de emitir um desafio quando ocorrer uma exceção em um


filtro de autorização.

Saiba mais sobre Autorização.


Filtros de recurso
Filtros de recurso:

Implementam a interface IResourceFilter ou IAsyncResourceFilter.


A execução encapsula grande 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 pipeline.
Por exemplo, um filtro de cache pode evitar o restante do pipeline em uma ocorrência
no cache.

Exemplos de filtros de recurso:

O filtro de recurso em curto-circuito mostrado anteriormente.

DisableFormValueModelBindingAttribute :
Impede o model binding de acessar os dados do formulário.
Usado para uploads de arquivos grandes para impedir que os dados de
formulário sejam lidos na memória.

Filtros de ação
Os filtros de ação não se aplicam a Razor Páginas. Razor O Pages dá IPageFilter suporte
a e IAsyncPageFilter. Para obter mais informações, consulte Métodos de filtro para Razor
Pages.

Filtros de ação:

Implementam a interface IActionFilter ou IAsyncActionFilter.


A execução deles envolve a execução de métodos de ação.

O código a seguir mostra um exemplo de filtro de ação:

C#

public class SampleActionFilter : IActionFilter


{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
}

public void OnActionExecuted(ActionExecutedContext context)


{
// Do something after the action executes.
}
}

A classe ActionExecutingContext fornece as seguintes propriedades:

ActionArguments – permite ler as entradas para um método de ação.


Controller – permite a manipulação da instância do controlador.
Result – configuração de Result execução de curtos-circuitos do método de ação
e dos filtros de ação posteriores.

Gerar uma exceção em um método de ação:

Impede a execução de filtros subsequentes.


Ao contrário da configuração Result , é tratada como uma falha e não como um
resultado bem-sucedido.

A classe ActionExecutedContext fornece Controller e Result , além das seguintes


propriedades:

Canceled – verdadeiro se a execução da ação tiver sofrido curto-circuito por outro


filtro.
Exception – não será nulo se a ação ou um filtro de ação executado antes tiver
apresentado uma exceção. Definir essa propriedade como nulo:
Trata efetivamente a exceção.
Result é executado como se tivesse retornado do método de ação.

Para um IAsyncActionFilter , uma chamada para o ActionExecutionDelegate:

Executa todos os próximos filtros de ação e o método de ação.


Retorna ActionExecutedContext .

Para fazer um curto-circuito, atribua


Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result a uma instância de
resultado e não chame o next ( ActionExecutionDelegate ).

A estrutura fornece um ActionFilterAttribute abstrato que você pode colocar em uma


subclasse.

O filtro de ação OnActionExecuting pode ser usado para:

Validar o estado do modelo.


Retornar um erro se o estado for inválido.

C#
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}

7 Observação

Os controladores anotados com o atributo validam automaticamente o


[ApiController] estado do modelo e retornam uma resposta 400. Para obter mais

informações, veja Respostas automáticas HTTP 400.

O método OnActionExecuted é executado após o método de ação:

E pode ver e manipular os resultados da ação por meio da propriedade Result.


Canceled é definido como verdadeiro se a execução da ação tiver sofrido curto-
circuito por outro filtro.
Exception é definido como um valor não nulo se a ação ou um filtro de ação
posterior tiver apresentado uma exceção. Definindo Exception como nulo:
Trata efetivamente uma exceção.
ActionExecutedContext.Result é executado como se fosse retornado

normalmente do método de ação.

Filtros de exceção
Filtros de exceção:

Implementam IExceptionFilter ou IAsyncExceptionFilter.


Podem ser usados para implementar políticas de tratamento de erro comuns.

O filtro de exceção de exemplo a seguir exibe detalhes sobre exceções que ocorrem
quando o aplicativo está em desenvolvimento:

C#

public class SampleExceptionFilter : IExceptionFilter


{
private readonly IHostEnvironment _hostEnvironment;
public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
_hostEnvironment = hostEnvironment;

public void OnException(ExceptionContext context)


{
if (!_hostEnvironment.IsDevelopment())
{
// Don't display exception details unless running in
Development.
return;
}

context.Result = new ContentResult


{
Content = context.Exception.ToString()
};
}
}

O código a seguir testa o filtro de exceção:

C#

[TypeFilter(typeof(SampleExceptionFilter))]
public class ExceptionController : Controller
{
public IActionResult Index() =>
Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

Filtros de exceção:

Não têm eventos anteriores nem posteriores.


Implementam OnException ou OnExceptionAsync.
Manipule exceções sem tratamento que ocorrem na Razor criação de página ou
controlador, model binding, filtros de ação ou 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 manipular uma exceção, defina a ExceptionHandled propriedade true como ou


atribua a Result propriedade . 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.

Filtros de exceção:

São bons para interceptar as exceções que ocorrem nas ações.


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 apenas quando
o tratamento de erros for diferente com base no método de ação chamado. Por
exemplo, um 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 exibição podem
retornar uma página de erro como HTML.

Filtros de resultado
Filtros de resultado:

Implemente uma interface:


IResultFilter ou IAsyncResultFilter
IAlwaysRunResultFilter ou IAsyncAlwaysRunResultFilter
A execução deles envolve a execução de resultados de ação.

IResultFilter e IAsyncResultFilter
O código a seguir mostra um filtro de resultado de exemplo:

C#

public class SampleResultFilter : IResultFilter


{
public void OnResultExecuting(ResultExecutingContext context)
{
// Do something before the result executes.
}

public void OnResultExecuted(ResultExecutedContext context)


{
// Do something after the result executes.
}
}

O tipo de resultado que está sendo executado depende da ação. Uma ação que retorna
uma exibição inclui todo o processamento razor como parte do ViewResult que está
sendo executado. Um método de API pode executar alguma serialização como parte da
execução do resultado. Saiba mais sobre os resultados da ação.

Os filtros de resultado só são executados quando uma ação ou filtro de ação produz um
resultado de ação. Os filtros de resultado não são executados quando:

Um filtro de autorização ou filtro de recurso causa um curto-circuito no pipeline.


Um filtro de exceção trata uma exceção produzindo um resultado de ação.

O método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting pode fazer


o curto-circuito da execução do resultado da ação e dos filtros de resultados posteriores
definindo Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel como true .
Grave no objeto de resposta ao fazer um curto-circuito para evitar gerar uma resposta
vazia. Gerando uma exceção em IResultFilter.OnResultExecuting :

Impede a execução do resultado da ação e dos filtros subsequentes.


É tratado como uma falha em vez de um resultado bem-sucedido.

Quando o Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted método é


executado, a resposta provavelmente já foi enviada ao cliente. Se a resposta já tiver sido
enviada ao cliente, ela não poderá ser alterada.

ResultExecutedContext.Canceled será definido como true 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 gerado uma exceção. A configuração
Exception como nulo manipula efetivamente uma exceção e impede que a exceção seja

lançada novamente mais tarde no pipeline. Não há nenhuma maneira confiável para
gravar dados em uma resposta ao manipular uma exceção em um filtro de resultado. Se
os cabeçalhos tiverem sido liberados para o cliente quando o resultado de uma ação
gerar uma exceção, não haverá 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 curto-circuito,
defina ResultExecutingContext.Cancel como true e não chame
: ResultExecutionDelegate

C#

public class SampleAsyncResultFilter : IAsyncResultFilter


{
public async Task OnResultExecutionAsync(
ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.Result is not EmptyResult)
{
await next();
}
else
{
context.Cancel = true;
}
}
}

A estrutura fornece um ResultFilterAttribute abstrato que você pode colocar em uma


subclasse. A classe ResponseHeaderAttribute mostrada anteriormente é um exemplo de
um atributo de filtro de resultado.

IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter
As interfaces IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter declaram uma
implementação IResultFilter que é executada para todos os resultados da ação. Isso
inclui os resultados da ação produzidos por:

Filtros de autorização e filtros de recursos que fazem o curto-circuito.


Filtros de exceção.

Por exemplo, o filtro a seguir sempre é executado e define o resultado de uma ação
(ObjectResult) com um código de status 422 Entidade Não Processável quando ocorre
falha na negociação de conteúdo:

C#

public class UnprocessableResultFilter : IAlwaysRunResultFilter


{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is StatusCodeResult statusCodeResult
&& statusCodeResult.StatusCode ==
StatusCodes.Status415UnsupportedMediaType)
{
context.Result = new ObjectResult("Unprocessable")
{
StatusCode = StatusCodes.Status422UnprocessableEntity
};
}
}

public void OnResultExecuted(ResultExecutedContext context) { }


}

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 o runtime se prepara para invocar o filtro, 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 invocada. Isso fornece um design


flexível, porque o pipeline de filtro preciso não precisa ser definido explicitamente
quando o aplicativo é iniciado.

IFilterFactory.IsReusable :

É uma dica da fábrica de que a instância de filtro criada pela fábrica pode ser
reutilizado fora do escopo da solicitação em que foi criada.
Não deve ser usado com um filtro que dependa de serviços com um tempo de
vida diferente de singleton.

O runtime do ASP.NET Core não garante:

Que uma única instância do filtro será criada.


Que o filtro não será solicitado novamente no contêiner de DI em algum momento
posterior.

2 Aviso

Configure IFilterFactory.IsReusable apenas para retornar true se a origem dos


filtros for inequívoca, os filtros forem sem estado e os filtros forem seguros para
usar em várias solicitações HTTP. Por exemplo, não retorne filtros de DI registrados
como com escopo ou transitórios se IFilterFactory.IsReusable retornarem true .

Implemente IFilterFactory usando implementações personalizadas de atributo como


outra abordagem à criação de filtros:

C#

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory


{
public bool IsReusable => false;

public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)


=>
new InternalResponseHeaderFilter();

private class InternalResponseHeaderFilter : IActionFilter


{
public void OnActionExecuting(ActionExecutingContext context) =>
context.HttpContext.Response.Headers.Add(
nameof(OnActionExecuting),
nameof(InternalResponseHeaderFilter));
public void OnActionExecuted(ActionExecutedContext context) { }
}

O filtro é aplicado no seguinte código:

C#

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");

IFilterFactory implementado em um atributo


Filtros que implementam IFilterFactory são úteis para filtros que:

Não exigem a passagem de parâmetros.


Tenha dependências de construtor que precisem ser atendidas pela DI.

TypeFilterAttribute implementa IFilterFactory. IFilterFactory expõe o método


CreateInstance para criar uma instância de IFilterMetadata. CreateInstance carrega o
tipo especificado do contêiner de serviços (DI).

C#

public class SampleActionTypeFilterAttribute : TypeFilterAttribute


{
public SampleActionTypeFilterAttribute()
: base(typeof(InternalSampleActionFilter)) { }

private class InternalSampleActionFilter : IActionFilter


{
private readonly ILogger<InternalSampleActionFilter> _logger;

public
InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
_logger = logger;

public void OnActionExecuting(ActionExecutingContext context)


{
_logger.LogInformation(
$"- {nameof(InternalSampleActionFilter)}.
{nameof(OnActionExecuting)}");
}

public void OnActionExecuted(ActionExecutedContext context)


{
_logger.LogInformation(
$"- {nameof(InternalSampleActionFilter)}.
{nameof(OnActionExecuted)}");
}
}
}

O código a seguir mostra três abordagens para aplicar o filtro:

C#

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
Content($"- {nameof(FilterFactoryController)}.
{nameof(WithDirectAttribute)}");

[TypeFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithTypeFilterAttribute() =>
Content($"- {nameof(FilterFactoryController)}.
{nameof(WithTypeFilterAttribute)}");

[ServiceFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithServiceFilterAttribute() =>
Content($"- {nameof(FilterFactoryController)}.
{nameof(WithServiceFilterAttribute)}");

No código anterior, a primeira abordagem para aplicar o filtro é preferencial.

Usar middleware no pipeline de filtro


Filtros de recursos funcionam como middleware, no sentido em que envolvem a
execução de tudo o que vem depois no pipeline. Mas os filtros diferem do middleware,
pois fazem parte do runtime, o que significa que eles têm acesso ao contexto e aos
constructos.

Para usar o middleware como um filtro, crie um tipo com um método Configure que
especifica o middleware para injeção no pipeline de filtros. O exemplo a seguir usa
middleware para definir um cabeçalho de resposta:

C#

public class FilterMiddlewarePipeline


{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Pipeline", "Middleware");

await next();
});
}
}

Use MiddlewareFilterAttribute para executar o middleware:

C#

[MiddlewareFilter(typeof(FilterMiddlewarePipeline))]
public class FilterMiddlewareController : Controller
{
public IActionResult Index() =>
Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

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.

Acesso thread-safe
Ao passar uma instância de um filtro para Add , em vez de seu Type , o filtro é um
singleton e não é thread-safe.

Recursos adicionais
Exibir ou baixar exemplo (como baixar).
Filtrar métodos para Razor Páginas no ASP.NET Core
Razor SDK do ASP.NET Core
Artigo • 10/01/2023 • 16 minutos para o fim da leitura

De Rick Anderson

Visão geral
O SDK do .NET 6.0 inclui o SDK do Microsoft.NET.Sdk.Razor MSBuild (Razor SDK). O
Razor SDK:

É necessário criar, empacotar e publicar projetos que Razor contenham arquivos


para ASP.NET Core projetos baseados Blazor em MVC.
Inclui um conjunto de propriedades predefinidas e itens que permitem
personalizar a compilação de Razor arquivos ( .cshtml ou .razor ).

O Razor SDK inclui Content itens com Include atributos definidos para os **\*.cshtml
padrões e **\*.razor globbing. Arquivos correspondentes são publicados.

Pré-requisitos
SDK do .NET 6.0

Usar o Razor SDK


A maioria dos aplicativos Web não é necessária para referenciar explicitamente o Razor
SDK.

Para usar o Razor SDK para criar bibliotecas de classes que Razor contêm exibições ou
Razor Páginas, recomendamos começar com o Razor modelo de projeto RCL (biblioteca
de classes). Uma RCL usada para criar Blazor arquivos ( .razor ) requer minimamente
uma referência ao pacote Microsoft.AspNetCore.Components . Uma RCL usada para
criar Razor exibições ou páginas ( .cshtml arquivos) requer minimamente
direcionamento netcoreapp3.0 ou posterior e tem um FrameworkReference para o
metapacote Microsoft.AspNetCore.App em seu arquivo de projeto.

Propriedades
As propriedades a seguir controlam o Razorcomportamento do SDK como parte de um
build de projeto:
RazorCompileOnBuild : quando true , compila e emite o Razor assembly como parte

da criação do projeto. Assume o padrão de true .


RazorCompileOnPublish : quando true , compila e emite o Razor assembly 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 Razor SDK.

Itens Descrição

RazorGenerate Elementos de item ( .cshtml arquivos) que são entradas para a geração
de código.

RazorComponent Elementos de item ( .razor arquivos) que são entradas para a Razor
geração de código de componente.

RazorCompile Elementos de item ( .cs arquivos) que são entradas para Razor destinos
de compilação. Use isso ItemGroup para especificar arquivos adicionais a
serem compilados no Razor assembly.

RazorEmbeddedResource Elementos de item adicionados como recursos inseridos ao assembly


gerado Razor .

Propriedade Descrição

RazorOutputPath O Razor diretório de saída.

RazorCompileToolset Usado para determinar o conjunto de


ferramentas usado para compilar o Razor
assembly. Os valores válidos são Implicit ,
RazorSDK e PrecompilationTool .

EnableDefaultContentItems O padrão é true . Quando true , inclui


arquivosweb.config, .json e .cshtml como
conteúdo no projeto. Quando referenciados
por meio Microsoft.NET.Sdk.Web de , os
arquivos em wwwroot e arquivos de
configuração também são incluídos.

EnableDefaultRazorGenerateItems Quando true , inclui .cshtml arquivos de


Content itens em RazorGenerate itens.

GenerateRazorTargetAssemblyInfo Não usado no .NET 6 e posterior.

EnableDefaultRazorTargetAssemblyInfoAttributes Não usado no .NET 6 e posterior.


Propriedade Descrição

CopyRazorGenerateFilesToPublishDirectory Quando true , copia RazorGenerate arquivos


de itens ( .cshtml ) para o diretório de
publicação. Normalmente, Razor os arquivos
não são necessários para um aplicativo
publicado se eles participam da compilação
em tempo de build ou em tempo de
publicação. Assume o padrão de false .

PreserveCompilationReferences Quando true , copia os itens do assembly de


referência no diretório de publicação.
Normalmente, assemblies de referência não
são necessários para um aplicativo publicado
se Razor a compilação ocorrer em tempo de
build ou em tempo de publicação. Defina
como true se seu aplicativo publicado exigir
compilação de runtime. Por exemplo, defina o
valor como true se o aplicativo modificar
.cshtml arquivos em runtime ou usar
exibições inseridas. Assume o padrão de
false .

IncludeRazorContentInPack Quando true , todos os Razor itens de


conteúdo ( .cshtml arquivos) são marcados
para inclusão no pacote NuGet gerado.
Assume o padrão de false .

EmbedRazorGenerateSources Quando true , adiciona RazorGerar ( .cshtml )


itens como arquivos inseridos ao assembly
gerado Razor . Assume o padrão de false .

GenerateMvcApplicationPartsAssemblyAttributes Não usado no .NET 6 e posterior.

DefaultWebContentItemExcludes Um padrão de globbing para elementos de


item que devem ser excluídos do Content
grupo de itens em projetos direcionados à
Web ou Razor ao SDK

ExcludeConfigFilesFromBuildOutput Quando true , .config e .json arquivos não


são copiados para o diretório de saída de
build.
Propriedade Descrição

AddRazorSupportForMvc Quando true , configura o Razor SDK para


adicionar suporte à configuração do MVC
necessária ao criar aplicativos que contêm
exibições MVC ou Razor Páginas. Essa
propriedade é definida implicitamente para
projetos .NET Core 3.0 ou posteriores
direcionados ao SDK da Web

RazorLangVersion A versão da Razor Linguagem a ser


direcionada.

EmitCompilerGeneratedFiles Quando definido como true , os arquivos de


origem gerados são gravados em disco. A
configuração como true é útil ao depurar o
compilador. O padrão é false .

Para saber mais sobre as propriedades, confira Propriedades do MSBuild.

Compilação de modos de exibição em Razor runtime


Por padrão, o Razor SDK não publica assemblies de referência necessários para
executar a compilação de runtime. Isso resulta em falhas de compilação quando o
modelo de aplicativo depende da compilação de runtime, por exemplo, o
aplicativo usa exibições inseridas ou altera exibições após a publicação do
aplicativo. Defina CopyRefAssembliesToPublishDirectory como true para continuar
publicando assemblies de referência. A geração de código e a compilação têm
suporte por uma única chamada para o compilador. Um único assembly é
produzido que contém os tipos de aplicativo e as exibições geradas.

Para um aplicativo Web, verifique se seu aplicativo está direcionando o


Microsoft.NET.Sdk.Web SDK.

Razor versão do idioma


Ao direcionar o Microsoft.NET.Sdk.Web SDK, a Razor versão do idioma é inferida da
versão da estrutura de destino do aplicativo. Para projetos direcionados ao
Microsoft.NET.Sdk.Razor SDK ou no caso raro em que o aplicativo requer uma versão
de idioma diferente Razor do valor inferido, uma versão pode ser configurada definindo
a <RazorLangVersion> propriedade no arquivo de projeto do aplicativo:

XML
<PropertyGroup>
<RazorLangVersion>{VERSION}</RazorLangVersion>
</PropertyGroup>

RazorA versão da linguagem é fortemente integrada à versão do runtime para a qual foi
criada. O direcionamento de uma versão de linguagem que não foi projetada para o
runtime não tem suporte e provavelmente produz erros de build.

Recursos adicionais
Adições ao formato csproj para .NET Core
Itens de projeto comuns do MSBuild
Componentes de exibição no ASP.NET
Core
Artigo • 04/01/2023 • 26 minutos para o fim da leitura

De Rick Anderson

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 associação de modelo, eles
dependem dos dados passados ao chamar o componente de exibição. Este artigo foi
escrito usando controladores e exibições, mas os componentes de exibição funcionam
com Razor o 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 lugar que a lógica de renderização


reutilizável seja muito complexa para uma exibição parcial, como:

Menus de navegação dinâmica


Tag cloud, onde ele consulta o banco de dados
Painel de entrada
Carrinho de compras
Artigos publicados recentemente
Conteúdo da barra lateral em um blog
Um painel de entrada que seria renderizado em cada página e mostraria os links
para sair ou entrar, dependendo do estado de entrada do usuário

Um componente de exibição consiste em duas partes:

A classe, normalmente derivada de ViewComponent


O resultado que ele retorna, normalmente um modo de exibição.

Assim como os controladores, um componente de exibição pode ser um POCO, mas a


maioria dos desenvolvedores aproveita os métodos e as propriedades disponíveis
derivando de ViewComponent.

Ao considerar se os componentes de exibição atendem às especificações de um


aplicativo, considere usar Razor componentes. Razor os componentes também
combinam marcação com código C# para produzir unidades de interface do usuário
reutilizáveis. Razor os componentes são projetados para produtividade do
desenvolvedor ao fornecer a lógica e a composição da interface do usuário do lado do
cliente. Para obter mais informações, consulte ASP.NET Core Razor componentes. Para
obter informações sobre como incorporar Razor componentes em um aplicativo MVC
ou Razor Pages, consulte Prerender e integre ASP.NET Core Razor componentes.

Criar um componente de exibição


Esta seção contém os requisitos de alto nível para a criação de um componente de
exibição. Mais adiante neste artigo, examinaremos cada etapa em detalhes e criaremos
um componente de exibição.

A classe de componente de exibição


Uma classe de componente de exibição pode ser criada por um dos seguintes:

Derivando de ViewComponent
Decoração de uma classe com o atributo [ViewComponent] ou derivação de uma
classe com o atributo [ViewComponent]
Criando uma classe em que o nome termina com o sufixo ViewComponent

Assim como os controladores, os componentes de exibição precisam ser classes


públicas, não aninhadas e não abstratas. O nome do componente de exibição é o nome
da classe com o ViewComponent sufixo removido. Também pode ser especificado de
forma explícita com a propriedade Name.

Uma classe de componente de exibição:

Dá suporte à injeção de dependência do construtor


Não participa do ciclo de vida do controlador, portanto, os filtros não podem ser
usados em um componente de exibição

Para impedir que uma classe que tenha um sufixo que não diferencia maiúsculas
ViewComponent de minúsculas seja tratada como um componente de exibição, decore a
classe com o [NonViewComponent] atributo:

C#
using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

Métodos de componente de exibição


Um componente de exibição define sua lógica em um:

InvokeAsync método que retorna Task<IViewComponentResult> .

Invoke método síncrono que retorna um IViewComponentResult.

Os parâmetros são recebidos diretamente da invocação do componente de exibição,


não do model binding. Um componente de exibição nunca manipula uma solicitação
diretamente. Normalmente, um componente de exibição inicializa um modelo e passa-o
para uma exibição chamando o método View . Em resumo, os métodos de componente
de exibição:

Definem um método InvokeAsync que retorna um Task<IViewComponentResult> ou


um método Invoke síncrono que retorna um IViewComponentResult .
Normalmente inicializa um modelo e o passa para um modo de exibição
chamando o método ViewComponent.View .
Os parâmetros são recebidos do método de chamada, não do HTTP. Não há
nenhum model binding.
Não podem ser acessados diretamente como um ponto de extremidade HTTP.
Normalmente, eles são invocados em uma exibição. Um componente de exibição
nunca manipula uma solicitação.
São sobrecarregados na assinatura, em vez de nos detalhes da solicitação HTTP
atual.

Caminho de pesquisa de exibição


O runtime pesquisa a exibição nos seguintes caminhos:

/Views/{Nome do Controlador}/Components/{Nome do Componente da


Exibição}/{Nome da Exibição}
/Views/Shared/Components/{Nome do Componente da Exibição}/{Nome da
Exibição}
/Pages/Shared/Components/{Nome do Componente da Exibição}/{Nome da
Exibição}

O caminho de pesquisa se aplica a projetos usando controladores + exibições e Razor


páginas.

O nome de exibição padrão de um componente de exibição é Default , o que significa


que os arquivos de exibição normalmente serão nomeados Default.cshtml . Um nome
de exibição diferente pode ser especificado ao criar o resultado do componente de
exibição ou ao chamar o View método.

Recomendamos nomear o arquivo Default.cshtml de exibição e usar o caminho


Views/Shared/Components/{View Component Name}/{View Name} . O PriorityList
componente de exibição usado neste exemplo usa
Views/Shared/Components/PriorityList/Default.cshtml para a exibição do componente
de exibição.

Personalizar o caminho de pesquisa de exibição


Para personalizar o caminho de pesquisa de exibição, modifique Razora coleção de 's
ViewLocationFormats . Por exemplo, para pesquisar exibições dentro do caminho
/Components/{View Component Name}/{View Name} , adicione um novo item à coleção:

C#

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
.AddRazorOptions(options =>
{
options.ViewLocationFormats.Add("/{0}.cshtml");
});

builder.Services.AddDbContext<ToDoContext>(options =>
options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

No código anterior, o espaço reservado {0} representa o caminho Components/{View


Component Name}/{View Name} .
Invocar um componente de exibição
Para usar o componente de exibição, chame o seguinte em uma exibição:

CSHTML

@await Component.InvokeAsync("Name of view component",


{Anonymous Type Containing Parameters})

Os parâmetros são passados para o InvokeAsync método. O PriorityList componente


de exibição desenvolvido no artigo é invocado do arquivo de exibição
Views/ToDo/Index.cshtml . No código a seguir, o InvokeAsync método é chamado com

dois parâmetros:

CSHTML

</table>

<div>
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>

Invocar um componente de exibição como


auxiliar de marca
Um componente view pode ser invocado como um auxiliar de marca:

CSHTML

<div>
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@{
int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
bool isDone = Convert.ToBoolean(ViewData["isDone"]);
}
<vc:priority-list max-priority=maxPriority is-done=isDone>
</vc:priority-list>
</div>
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:

CSHTML

<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 o
componente de exibição estiver em um assembly chamado MyWebApp , adicione a
seguinte diretiva ao _ViewImports.cshtml arquivo:

CSHTML

@addTagHelper *, MyWebApp

Um componente de exibição pode ser registrado como auxiliar de marca para qualquer
arquivo que faça referência ao 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:

CSHTML

@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)

Na marcação anterior, o componente de exibição PriorityList se torna priority-list .


Os parâmetros para o componente de exibição são passados como atributos em kebab
case.

Invocar um componente de exibição diretamente de um


controlador
Normalmente, os componentes de exibição são invocados de um modo de exibição,
mas podem ser invocados diretamente de um método de controlador. Embora os
componentes de exibição não definam pontos de extremidade como controladores,
uma ação do controlador que retorna o conteúdo de um ViewComponentResult pode ser
implementada.

No exemplo a seguir, o componente de exibição é chamado diretamente do


controlador:

C#

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)


{
return ViewComponent("PriorityList",
new {
maxPriority = maxPriority,
isDone = isDone
});
}

Criar um componente de exibição básico


Baixe , compile e teste o código inicial. É um projeto básico com um ToDo controlador
que exibe uma lista de itens ToDo .
Atualizar o controlador para passar o status de prioridade
e de conclusão
Atualize o Index método para usar parâmetros de status de prioridade e de conclusão:

C#

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
private readonly ToDoContext _ToDoContext;

public ToDoController(ToDoContext context)


{
_ToDoContext = context;
_ToDoContext.Database.EnsureCreated();
}

public IActionResult Index(int maxPriority = 2, bool isDone = false)


{
var model = _ToDoContext!.ToDo!.ToList();
ViewData["maxPriority"] = maxPriority;
ViewData["isDone"] = isDone;
return View(model);
}

Adicionar uma classe ViewComponent


Adicione uma classe ViewComponent a ViewComponents/PriorityListViewComponent.cs :

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityListViewComponent : ViewComponent


{
private readonly ToDoContext db;

public PriorityListViewComponent(ToDoContext context) => db = context;

public async Task<IViewComponentResult> InvokeAsync(


int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}

private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)


{
return db!.ToDo!.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}

Observações sobre o código:

As classes de componente de exibição podem ser contidas em qualquer pasta do


projeto.

Como o nome da classe PriorityListViewComponent termina com o sufixo


ViewComponent, o runtime usa a cadeia PriorityList de caracteres ao
referenciar o componente de classe de um modo de exibição.

O atributo [ViewComponent] pode alterar o nome usado para referenciar um


componente de exibição. Por exemplo, a classe poderia ter sido nomeada XYZ com
o seguinte [ViewComponent] atributo:

C#

[ViewComponent(Name = "PriorityList")]
public class XYZ : ViewComponent

O [ViewComponent] atributo no código anterior informa ao seletor de


componentes de exibição a ser usado:
O nome PriorityList ao procurar as exibições associadas ao componente
A cadeia de caracteres "PriorityList" ao referenciar o componente de classe de
um modo de exibição.

O componente usa a injeção de dependência para disponibilizar o contexto de


dados.

InvokeAsync expõe um método que pode ser chamado de uma exibição e pode

ter um número arbitrário de argumentos.

O método InvokeAsync retorna o conjunto de itens ToDo que atendem aos


parâmetros isDone e maxPriority .

Criar a exibição do componente Razor de exibição


Crie a pasta Views/Shared/Components. Essa pasta deve nomeada Components.

Crie a pasta Views/Shared/Components/PriorityList. Esse nome de pasta deve


corresponder ao nome da classe de componente de exibição ou ao nome da
classe menos o sufixo. Se o ViewComponent atributo for usado, o nome da classe
precisará corresponder à designação de atributo.

Criar um modo de Views/Shared/Components/PriorityList/Default.cshtml Razor


exibição:

CSHTML

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h3>Priority Items</h3>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>

O Razor modo de exibição usa uma lista e TodoItem exibe-os. Se o método de


componente InvokeAsync de exibição não passar o nome da exibição, o padrão
será usado para o nome da exibição por convenção. Para substituir o estilo padrão
de um controlador específico, adicione uma exibição à pasta de exibição específica
do controlador (por exemplo, Views/ToDo/Components/PriorityList/Default.cshtml).

Se o componente de exibição for específico do controlador, ele poderá ser


adicionado à pasta específica do controlador. Por exemplo,
Views/ToDo/Components/PriorityList/Default.cshtml é específico do controlador.

Adicione uma div chamada contendo ao componente de lista de prioridades à


parte inferior do Views/ToDo/index.cshtml arquivo:

CSHTML

</table>

<div>
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>

A marcação @await Component.InvokeAsync mostra a sintaxe para chamar componentes


de exibição. O primeiro argumento é o nome do componente que queremos invocar ou
chamar. Os parâmetros seguintes são passados para o componente. InvokeAsync pode
usar um número arbitrário de argumentos.

Testar o aplicativo. A seguinte imagem mostra a lista ToDo e os itens de prioridade:

O componente de exibição pode ser chamado diretamente do controlador:

C#

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)


{
return ViewComponent("PriorityList",
new {
maxPriority = maxPriority,
isDone = isDone
});
}
Especificar um nome de componente de exibição
Um componente de exibição complexo pode precisar especificar uma exibição não
padrão em algumas condições. O código a seguir mostra como especificar a exibição
"PVC" do InvokeAsync método. Atualize o método InvokeAsync na classe
PriorityListViewComponent .

C#

public async Task<IViewComponentResult> InvokeAsync(


int maxPriority, bool isDone)
{
string MyView = "Default";
// If asking for all completed tasks, render with the "PVC" view.
if (maxPriority > 3 && isDone == true)
{
MyView = "PVC";
}
var items = await GetItemsAsync(maxPriority, isDone);
return View(MyView, items);
}

Copie o Views/Shared/Components/PriorityList/Default.cshtml arquivo para uma


exibição chamada Views/Shared/Components/PriorityList/PVC.cshtml . Adicione um
cabeçalho para indicar que a exibição PVC está sendo usada.

CSHTML

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>


<h4>@ViewBag.PriorityMessage</h4>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>

Execute o aplicativo e verifique a exibição PVC.

Se o modo de exibição PVC não for renderizado, verifique se o componente de exibição


com uma prioridade de 4 ou mais é chamado.

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 como
Views/ToDo/Components/PriorityList/Default.cshtml 1Default.cshtml .

Teste o aplicativo, o seguinte erro ocorre:

txt

An unhandled exception occurred while processing the request.


InvalidOperationException: The view 'Components/PriorityList/Default'
wasn't found. The following locations were searched:
/Views/ToDo/Components/PriorityList/Default.cshtml
/Views/Shared/Components/PriorityList/Default.cshtml

Copie Views/ToDo/Components/PriorityList/1Default.cshtml para


Views/Shared/Components/PriorityList/Default.cshtml .

Adicione uma marcação ao componente de exibição ToDo Shared para indicar que
a exibição foi obtida da pasta Shared.

Teste o componente de exibição Shared.


Evitar cadeias de caracteres codificadas
Para compilar a segurança de tempo, substitua o nome do componente de exibição
codificado pelo nome da classe. Atualize o arquivo PriorityListViewComponent.cs para
não usar o sufixo "ViewComponent":

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityList : ViewComponent


{
private readonly ToDoContext db;

public PriorityList(ToDoContext context)


{
db = context;
}

public async Task<IViewComponentResult> InvokeAsync(


int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}

private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)


{
return db!.ToDo!.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}

O arquivo de exibição:

CSHTML

</table>

<div>
Testing nameof(PriorityList) <br />

Maxium Priority: @ViewData["maxPriority"] <br />


Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(nameof(PriorityList),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>

Uma sobrecarga de Component.InvokeAsync método que usa um tipo CLR usa o typeof
operador:

CSHTML

</table>

<div>
Testing typeof(PriorityList) <br />

Maxium Priority: @ViewData["maxPriority"] <br />


Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(typeof(PriorityList),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>

Executar o trabalho síncrono


A estrutura manipula a invocação de um método síncrono Invoke se o trabalho
assíncrono não for necessário. O método a seguir cria um componente de exibição
Invoke síncrono:

C#

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
public class PriorityListSync : ViewComponent
{
private readonly ToDoContext db;

public PriorityListSync(ToDoContext context)


{
db = context;
}

public IViewComponentResult Invoke(int maxPriority, bool isDone)


{

var x = db!.ToDo!.Where(x => x.IsDone == isDone &&


x.Priority <= maxPriority).ToList();
return View(x);
}
}
}

O arquivo do componente de Razor exibição:

CSHTML

<div>
Testing nameof(PriorityList) <br />

Maxium Priority: @ViewData["maxPriority"] <br />


Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(nameof(PriorityListSync),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>

O componente de exibição é invocado em um Razor arquivo (por exemplo,


Views/Home/Index.cshtml ) usando uma das seguintes abordagens:

IViewComponentHelper
Auxiliar de Marca

Para usar a abordagem IViewComponentHelper, chame Component.InvokeAsync :

CSHTML

@await Component.InvokeAsync(nameof(PriorityList),
new { maxPriority = 4, isDone = true })

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 ):

CSHTML

@addTagHelper *, MyWebApp

Use o auxiliar de marca do componente de exibição no Razor arquivo de marcação:

CSHTML
<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

A assinatura do PriorityList.Invoke método é síncrona, mas Razor localiza e chama o


método no Component.InvokeAsync arquivo de marcação.

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Injeção de dependência em exibições
Exibir componentes em Razor páginas
Por que você deve usar componentes de exibição, não exibições parciais, em
ASP.NET Core
Razorcompilação de arquivo no ASP.NET
Core
Artigo • 17/12/2022 • 6 minutos para o fim da leitura

Razor os arquivos com uma .cshtml extensão são compilados em tempo de compilação
e publicação usando o Razor SDK. A compilação de runtime pode estar opcionalmente
habilitada configurando o projeto.

7 Observação

Compilação de runtime:

Não há suporte para Razor componentes de Blazor aplicativos.


Não dá suporte a diretivas globais de uso.
Não dá suporte ao uso implícito de diretivas

Razor Compilação
A compilação de arquivos em tempo de compilação e de tempo de Razor publicação é
habilitada por padrão pelo Razor SDK. Quando habilitada, a compilação de runtime
complementa a compilação em tempo de build, permitindo Razor que os arquivos
sejam atualizados se forem editados.

Além da compilação em tempo de build, há suporte para a atualização Razor de


exibições e Razor páginas usando o suporte do .NET Recarga Dinâmica para ASP.NET
Core.

7 Observação

Quando habilitada, a compilação de runtime desabilita atualmente o .NET Recarga


Dinâmica. A compilação de runtime com Recarga Dinâmica está planejada para
uma versão futura.

Habilitar a compilação de runtime para todos


os ambientes
Para habilitar a compilação de runtime para todos os ambientes:
1. Instale o Microsoft. AspNetCore.Mvc.Razor. Pacote NuGet runtimeCompilation .

2. Chame AddRazorRuntimeCompilation em Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages()
.AddRazorRuntimeCompilation();

Habilitar a compilação de runtime


condicionalmente
A compilação de runtime pode ser habilitada condicionalmente, o que garante que a
saída publicada:

Usa exibições compiladas.


Não habilita observadores de arquivos em produção.

Para habilitar a compilação de runtime somente para o ambiente de desenvolvimento:

1. Instale o Microsoft. AspNetCore.Mvc.Razor. Pacote NuGet runtimeCompilation .

2. Chame AddRazorRuntimeCompilation quando Program.cs o ambiente atual estiver


definido como Desenvolvimento:

C#

var builder = WebApplication.CreateBuilder(args);

var mvcBuilder = builder.Services.AddRazorPages();

if (builder.Environment.IsDevelopment())
{
mvcBuilder.AddRazorRuntimeCompilation();
}

A compilação de runtime também pode ser habilitada com um assembly de inicialização


de hospedagem. Para habilitar a compilação de runtime no ambiente de
desenvolvimento para perfis de inicialização específicos:

1. Instale o Microsoft. AspNetCore.Mvc.Razor. Pacote NuGet runtimeCompilation .


2. Modifique a seção do perfil de environmentVariables inicialização em
launchSettings.json :
Verifique se ASPNETCORE_ENVIRONMENT está definido como "Development" .

Defina ASPNETCORE_HOSTINGSTARTUPASSEMBLIES como


"Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" . Por exemplo, o

seguinte launchSettings.json habilita a compilação de runtime para os


ViewCompilationSample perfis de inicialização e IIS Express :

JSON

{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:7098",
"sslPort": 44332
}
},
"profiles": {
"ViewCompilationSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl":
"https://localhost:7173;http://localhost:5251",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES":
"Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES":
"Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
}
}
}
}

Com essa abordagem, nenhuma alteração de código é necessária em Program.cs . Em


runtime, ASP.NET Core pesquisa um atributo HostingStartup no nível do assembly em
Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation . O HostingStartup atributo
especifica o código de inicialização do aplicativo a ser executado e esse código de
inicialização habilita a compilação de runtime.
Habilitar a compilação de runtime para uma
Razor biblioteca de classes
Considere um cenário no qual um Razor projeto Pages referencia uma Razor RCL
(Biblioteca de Classes) chamada MyClassLib. A RCL contém um _Layout.cshtml arquivo
consumido por projetos MVC e Razor Pages. Para habilitar a compilação de runtime
para o _Layout.cshtml arquivo nessa RCL, faça as seguintes alterações no Razor projeto
Pages:

1. Habilite a compilação de runtime com as instruções em Habilitar a compilação de


runtime condicionalmente.

2. MvcRazorRuntimeCompilationOptions Configurar em Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MvcRazorRuntimeCompilationOptions>(options
=>
{
var libraryPath = Path.GetFullPath(
Path.Combine(builder.Environment.ContentRootPath, "..",
"MyClassLib"));

options.FileProviders.Add(new PhysicalFileProvider(libraryPath));
});

O código anterior cria um caminho absoluto para a RCL MyClassLib . A API


PhysicalFileProvider é usada para localizar diretórios e arquivos nesse caminho
absoluto. Por fim, a PhysicalFileProvider instância é adicionada a uma coleção de
provedores de arquivos, que permite o acesso aos arquivos da .cshtml RCL.

Recursos adicionais
RazorPropriedades CompileOnBuild e RazorCompileOnPublish
Introdução às Razor Páginas no ASP.NET Core
Exibições no ASP.NET Core MVC
Razor SDK do ASP.NET Core
Exibir e editor modelos no ASP.NET Core
Artigo • 04/01/2023 • 4 minutos para o fim da leitura

Por Alexander Wicht

Modelos de exibição e editor especificam o layout da interface do usuário de tipos


personalizados. Considere o seguinte modelo Address :

C#

public class Address


{
public int Id { get; set; }
public string FirstName { get; set; } = null!;
public string MiddleName { get; set; } = null!;
public string LastName { get; set; } = null!;
public string Street { get; set; } = null!;
public string City { get; set; } = null!;
public string State { get; set; } = null!;
public string Zipcode { get; set; } = null!;
}

Um projeto que faz scaffolds do Address modelo exibe o Address seguinte formulário:

Um site pode usar um Modelo de Exibição para mostrar o Address formato padrão:
Modelos de exibição e editor também podem reduzir os custos de duplicação e
manutenção de código. Considere um site que exibe o Address modelo em 20 páginas
diferentes. Se o Address modelo for alterado, todas as 20 páginas precisarão ser
atualizadas. Se um modelo de exibição for usado para o Address modelo, somente o
Modelo de Exibição precisará ser atualizado. Por exemplo, o Address modelo pode ser
atualizado para incluir o país.

Os Auxiliares de Marca fornecem uma maneira alternativa que permite que o código do
lado do servidor participe na criação e renderização de elementos HTML em Razor
arquivos. Para obter mais informações, consulte Auxiliares de Marca em comparação
com auxiliares HTML.

Exibir modelos
DisplayTemplates personalize a exibição de campos de modelo ou crie uma camada de

abstração entre os valores do modelo e sua exibição.

Um DisplayTemplate é um Razor arquivo colocado na DisplayTemplates pasta:

Para Razor aplicativos Pages, na Pages/Shared/DisplayTemplates pasta.


Para aplicativos MVC, na Views/Shared/DisplayTemplates pasta ou na
Views/ControllerName/DisplayTemplates pasta. Os modelos de exibição

Views/Shared/DisplayTemplates no são usados por todos os controladores no


aplicativo. Os modelos de exibição na Views/ControllerName/DisplayTemplates
pasta são resolvidos somente pelo ControllerName controlador.

Por convenção, o arquivo tem o DisplayTemplate nome do tipo a ser exibido. O


Address.cshtml modelo usado neste exemplo:
CSHTML

@model Address

<dl>
<dd>@Model.FirstName @Model.MiddleName @Model.LastName</dd>
<dd>@Model.Street</dd>
<dd>@Model.City @Model.State @Model.Zipcode</dd>
</dl>

O mecanismo de exibição procura automaticamente um arquivo na DisplayTemplates


pasta que corresponda ao nome do tipo. Se ele não encontrar um modelo
correspondente, ele retornará aos modelos internos.

O código a seguir mostra a exibição Detalhes do projeto scaffolded:

CSHTML

@page
@model WebAddress.Pages.Adr.DetailsModel

@{
ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
<h4>Address</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Address.FirstName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address.FirstName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Address.MiddleName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address.MiddleName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Address.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Address.Street)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address.Street)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Address.City)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address.City)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Address.State)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address.State)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Address.Zipcode)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address.Zipcode)
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Address?.Id">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>

O código a seguir mostra a exibição Detalhes usando o Modelo de Exibição de


Endereço:

CSHTML

@page
@model WebAddress.Pages.Adr2.DetailsModel

@{
ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
<h4>Address DM</h4>
<hr />
<dl class="row">
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address)
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Address?.Id">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>

Para fazer referência a um modelo cujo nome não corresponde ao nome do tipo, use o
templateName parâmetro no DisplayFor método. Por exemplo, a marcação a seguir exibe
o Address modelo com o AddressShort modelo:

CSHTML

@page
@model WebAddress.Pages.Adr2.DetailsCCModel

@{
ViewData["Title"] = "Details Short";
}

<h1>Details Short</h1>

<div>
<h4>Address Short</h4>
<hr />
<dl class="row">
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address,"AddressShort")
</dd>
</dl>
</div>

Use uma das sobrecargas do DisplayFor disponíveis que expõem o additionalViewData


parâmetro para passar dados de exibição adicionais que são mesclados na instância do
Dicionário de Dados de Exibição criada para o modelo.

Modelos de editor
Os modelos de editor são usados em controles de formulário quando o modelo é
editado ou atualizado.

Um EditorTemplate é um Razor arquivo colocado na EditorTemplates pasta:

Para Razor aplicativos Pages, na Pages/Shared/EditorTemplates pasta.


Para aplicativos MVC, na Views/Shared/EditorTemplates pasta ou na
Views/ControllerName/EditorTemplates pasta.

A marcação a seguir mostra o Pages/Shared/EditorTemplates/Address.cshtml usado no


exemplo:
CSHTML

@model Address

<dl>
<dd> Name:
<input asp-for="FirstName" /> <input asp-for="MiddleName" /> <input
asp-for="LastName" />
</dd>
<dd> Street:
<input asp-for="Street" />
</dd>

<dd> city/state/zip:
<input asp-for="City" /> <input asp-for="State" /> <input asp-
for="Zipcode" />
</dd>

</dl>

A marcação a seguir mostra a página Edit.cshtml que usa o


Pages/Shared/EditorTemplates/Address.cshtml modelo:

CSHTML

@page
@model WebAddress.Pages.Adr.EditModel

@{
ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Address</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="Address.Id" />
@Html.EditorFor(model => model.Address)
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Auxiliares de Marcas
Comparação entre Auxiliares de Marca e Auxiliares HTML
Carregar arquivos no ASP.NET Core
Artigo • 10/01/2023 • 74 minutos para o fim da leitura

Por Rutger Storm

ASP.NET Core dá suporte ao carregamento de um ou mais arquivos usando a


associação de modelo em buffer para arquivos menores e streaming não compilado
para arquivos maiores.

Exibir ou baixar código de exemplo (como baixar)

Considerações de segurança
Tenha cuidado ao fornecer aos usuários a capacidade de carregar arquivos em um
servidor. Os invasores podem tentar:

Executar ataques de negação de serviço .


Carregar vírus ou malware.
Comprometer redes e servidores de outras maneiras.

As etapas de segurança que reduzem a probabilidade de um ataque bem-sucedido são:

Carregue arquivos em uma área de carregamento de arquivo dedicada,


preferencialmente para uma unidade que não seja do sistema. Um local dedicado
facilita a imposição de restrições de segurança em arquivos carregados. Desabilitar
permissões de execução no local de carregamento de arquivo.†
Não persista arquivos carregados na mesma árvore de diretório que o app.†
Use um nome de arquivo seguro determinado pelo aplicativo. Não use um nome
de arquivo fornecido pelo usuário ou o nome de arquivo não confiável do arquivo
carregado.† HTML codifica o nome do arquivo não confiável ao exibi-lo. Por
exemplo, registrar o nome do arquivo ou exibi-lo na interface do usuário (Razor
codifica automaticamente a saída html).
Permitir somente extensões de arquivo aprovadas para a especificação de design
do aplicativo.†
Verifique se as verificações do lado do cliente são executadas no server.† as
verificações do lado do cliente são fáceis de contornar.
Verifique o tamanho de um arquivo carregado. Defina um limite de tamanho
máximo para evitar uploads grandes.†
Quando os arquivos não devem ser substituídos por um arquivo carregado com o
mesmo nome, verifique o nome do arquivo no banco de dados ou no
armazenamento físico antes de carregar o arquivo.
Execute um scanner de vírus/malware no conteúdo carregado antes que o
arquivo seja armazenado.

†O aplicativo de exemplo demonstra uma abordagem que atende aos critérios.

2 Aviso

Carregar códigos mal-intencionados em um sistema é frequentemente a primeira


etapa para executar o código que pode:

Obtenha completamente o controle de um sistema.


Sobrecarregue um sistema com o resultado de falha do sistema.
Comprometer dados do sistema ou de usuários.
Aplicar grafite a uma interface do usuário pública.

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

Para obter mais informações sobre como implementar medidas de segurança, incluindo
exemplos do aplicativo de exemplo, consulte a seção Validação .

Cenários de armazenamento
As opções comuns de armazenamento para arquivos incluem:

Banco de dados
Para carregamentos de arquivos pequenos , um banco de dados geralmente é
mais rápido do que as opções de armazenamento físico (sistema de arquivos ou
compartilhamento de rede).
Um banco de dados geralmente é mais conveniente do que as opções de
armazenamento físico porque a recuperação de um registro de banco de dados
para dados do usuário pode fornecer simultaneamente o conteúdo do arquivo
(por exemplo, uma imagem de avatar).
Um banco de dados é potencialmente menos caro do que usar um serviço de
armazenamento de dados em nuvem.

Armazenamento físico (sistema de arquivos ou compartilhamento de rede)


Para carregamentos de arquivos grandes:
Os limites de banco de dados podem restringir o tamanho do upload.
O armazenamento físico geralmente é menos econômico do que o
armazenamento em um banco de dados.
O armazenamento físico é potencialmente menos caro do que usar um serviço
de armazenamento de dados em nuvem.
O processo do aplicativo deve ter permissões de leitura e gravação no local de
armazenamento. Nunca conceda permissão de execução.

O serviço de armazenamento de dados de nuvem, por exemplo, Armazenamento


de Blobs do Azure .
Os serviços geralmente oferecem escalabilidade e resiliência aprimoradas em
soluções locais que geralmente estão sujeitas a pontos únicos de falha.
Os serviços são um custo potencialmente menor em grandes cenários de
infraestrutura de armazenamento.

Para obter mais informações, consulte Início Rápido: usar o .NET para criar um
blob no armazenamento de objetos.

Arquivos pequenos e grandes


A definição de arquivos pequenos e grandes depende dos recursos de computação
disponíveis. Os aplicativos devem fazer o benchmark da abordagem de armazenamento
usada para garantir que ele possa lidar com os tamanhos esperados. Memória de
benchmark, CPU, disco e desempenho do banco de dados.

Embora limites específicos não possam ser fornecidos no que é pequeno versus grande
para sua implantação, aqui estão alguns dos padrões relacionados do AspNetCore para
FormOptions :

Por padrão, HttpRequest.Form não armazena em buffer todo o corpo da


solicitação (BufferBody), mas armazena em buffer todos os arquivos de formulário
de várias partes incluídos.
MultipartBodyLengthLimit é o tamanho máximo para arquivos de formulário
armazenados em buffer, o padrão é 128 MB.
MemoryBufferThreshold indica quanto armazenar arquivos em buffer na memória
antes de fazer a transição para um arquivo de buffer no disco, o padrão é 64KB.
MemoryBufferThreshold atua como um limite entre arquivos pequenos e grandes

que são gerados ou reduzidos dependendo dos recursos e cenários dos


aplicativos.

Para obter mais informações sobre FormOptions , consulte o código-fonte .


Cenários de carregamento de arquivo
Duas abordagens gerais para carregar arquivos são buffer e streaming.

de resposta

O arquivo inteiro é lido em um IFormFile. IFormFile é uma representação C# do arquivo


usado para processar ou salvar o arquivo.

O disco e a memória usados pelos uploads de arquivo dependem do número e do


tamanho dos uploads de arquivos simultâneos. Se um aplicativo tentar armazenar em
buffer muitos uploads, o site falhará quando ficar sem memória ou espaço em disco. Se
o tamanho ou a frequência de uploads de arquivos estiver esgotando os recursos do
aplicativo, use streaming.

Qualquer arquivo armazenado em buffer que exceda 64 KB é movido da memória para


um arquivo temporário no disco.

Arquivos temporários para solicitações maiores são gravados no local nomeado na


variável de ASPNETCORE_TEMP ambiente. Se ASPNETCORE_TEMP não estiver definido, os
arquivos serão gravados na pasta temporária do usuário atual.

O buffer de arquivos pequenos é abordado nas seções a seguir deste tópico:

Armazenamento físico
Backup de banco de dados

Streaming

O arquivo é recebido de uma solicitação de várias partes e processado ou salvo


diretamente pelo aplicativo. O streaming não melhora significativamente o
desempenho. O streaming reduz as demandas por memória ou espaço em disco ao
carregar arquivos.

O streaming de arquivos grandes é abordado na seção Carregar arquivos grandes com


streaming .

Carregar arquivos pequenos com associação de modelo


em buffer para armazenamento físico
Para carregar arquivos pequenos, use um formulário de várias partes ou construa uma
solicitação POST usando JavaScript.
O exemplo a seguir demonstra o uso de um Razor formulário Pages para carregar um
único arquivo ( Pages/BufferedSingleFileUploadPhysical.cshtml no aplicativo de
exemplo):

CSHTML

<form enctype="multipart/form-data" method="post">


<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit"
value="Upload" />
</form>

O exemplo a seguir é análogo ao exemplo anterior, exceto que:

A API do JavaScript (Fetch) é usada para enviar os dados do formulário.


Não há validação.

CSHTML

<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return
false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>

<input class="btn" type="submit" value="Upload" />

<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>

<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);

try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});

if (response.ok) {
window.location.href = '/';
}

resultElement.value = 'Result: ' + response.status + ' ' +


response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>

Para executar o formulário POST em JavaScript para clientes que não dão suporte à API
fetch , use uma das seguintes abordagens:

Use um Polyfill fetch (por exemplo, polyfill window.fetch (github/fetch) ).

Use XMLHttpRequest . Por exemplo:

JavaScript

<script>
"use strict";

function AJAXSubmit (oFormElement) {


var oReq = new XMLHttpRequest();
oReq.onload = function(e) {
oFormElement.elements.namedItem("result").value =
'Result: ' + this.status + ' ' + this.statusText;
};
oReq.open("post", oFormElement.action);
oReq.send(new FormData(oFormElement));
}
</script>

Para dar suporte a uploads de arquivo, os formulários HTML devem especificar um tipo
de codificação ( enctype ) de multipart/form-data .

Para que um files elemento de entrada dê suporte ao carregamento de vários


arquivos, forneça o multiple atributo no <input> elemento :

CSHTML
<input asp-for="FileUpload.FormFiles" type="file" multiple>

Os arquivos individuais carregados no servidor podem ser acessados por meio da


Associação de Modelo usando IFormFile. O aplicativo de exemplo demonstra vários
uploads de arquivos em buffer para cenários de armazenamento físico e de banco de
dados.

2 Aviso

Não use a FileName propriedade de diferente de IFormFile para exibição e registro


em log. Ao exibir ou registrar em log, o HTML codifica o nome do arquivo. Um
invasor pode fornecer um nome de arquivo mal-intencionado, incluindo caminhos
completos ou caminhos relativos. Os aplicativos devem:

Remova o caminho do nome de arquivo fornecido pelo usuário.


Salve o nome de arquivo codificado em HTML e removido pelo caminho para
interface do usuário ou registro em log.
Gere um novo nome de arquivo aleatório para armazenamento.

O código a seguir remove o caminho do nome do arquivo:

C#

string untrustedFileName = Path.GetFileName(pathName);

Os exemplos fornecidos até agora não levam em conta as considerações de


segurança. Informações adicionais são fornecidas pelas seções a seguir e pelo
aplicativo de exemplo :

Considerações sobre segurança


Validação

Ao carregar arquivos usando model binding e IFormFile, o método de ação pode


aceitar:

Um único IFormFile.
Qualquer uma das seguintes coleções que representam vários arquivos:
IFormFileCollection
IEnumerable<IFormFile>
Lista<IFormFile>
7 Observação

A associação corresponde aos arquivos de formulário por nome. Por exemplo, o


valor HTML name em <input type="file" name="formFile"> deve corresponder ao
parâmetro/propriedade C# associado ( FormFile ). Para obter mais informações,
consulte a seção Corresponder o valor do atributo de nome ao nome do
parâmetro do método POST .

O exemplo a seguir:

Executa um loop em um ou mais arquivos carregados.


Usa Path.GetTempFileName para retornar um caminho completo para um arquivo,
incluindo o nome do arquivo.
Salva os arquivos no sistema de arquivos local usando um nome de arquivo
gerado pelo aplicativo.
Retorna o número total e o tamanho dos arquivos carregados.

C#

public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)


{
long size = files.Sum(f => f.Length);

foreach (var formFile in files)


{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();

using (var stream = System.IO.File.Create(filePath))


{
await formFile.CopyToAsync(stream);
}
}
}

// Process uploaded files


// Don't rely on or trust the FileName property without validation.

return Ok(new { count = files.Count, size });


}

Use Path.GetRandomFileName para gerar um nome de arquivo sem um caminho. No


exemplo a seguir, o caminho é obtido da configuração:

C#
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());

using (var stream = System.IO.File.Create(filePath))


{
await formFile.CopyToAsync(stream);
}
}
}

O caminho passado para o FileStreamdeve incluir o nome do arquivo. Se o nome do


arquivo não for fornecido, um UnauthorizedAccessException será lançado em runtime.

Os arquivos carregados usando a IFormFile técnica são armazenados em buffer na


memória ou no disco no servidor antes do processamento. Dentro do método de ação,
o IFormFile conteúdo é acessível como um Stream. Além do sistema de arquivos local,
os arquivos podem ser salvos em um compartilhamento de rede ou em um serviço de
armazenamento de arquivos, como o Armazenamento de Blobs do Azure.

Para obter outro exemplo que executa um loop em vários arquivos para upload e usa
nomes de arquivo seguros, consulte
Pages/BufferedMultipleFileUploadPhysical.cshtml.cs no aplicativo de exemplo.

2 Aviso

Path.GetTempFileName gerará um IOException se mais de 65.535 arquivos forem


criados sem excluir arquivos temporários anteriores. O limite de 65.535 arquivos é
um limite por servidor. Para obter mais informações sobre esse limite no sistema
operacional Windows, consulte os comentários nos seguintes tópicos:

Função GetTempFileNameA
GetTempFileName

Carregar arquivos pequenos com a associação de modelo


em buffer para um banco de dados
Para armazenar dados de arquivo binário em um banco de dados usando o Entity
Framework, defina uma Byte propriedade de matriz na entidade:

C#
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}

Especifique uma propriedade de modelo de página para a classe que inclui um


IFormFile:

C#

public class BufferedSingleFileUploadDbModel : PageModel


{
...

[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }

...
}

public class BufferedSingleFileUploadDb


{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}

7 Observação

IFormFile pode ser usado diretamente como um parâmetro de método de ação ou


como uma propriedade de modelo associada. O exemplo anterior usa uma
propriedade de modelo associada.

O FileUpload é usado no Razor formulário Páginas:

CSHTML

<form enctype="multipart/form-data" method="post">


<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit"
value="Upload">
</form>

Quando o formulário for POSTed no servidor, copie o IFormFile para um fluxo e salve-o
como uma matriz de bytes no banco de dados. No exemplo a seguir, _dbContext
armazena o contexto de banco de dados do aplicativo:

C#

public async Task<IActionResult> OnPostUploadAsync()


{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);

// Upload the file if less than 2 MB


if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};

_dbContext.File.Add(file);

await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}

return Page();
}

O exemplo anterior é semelhante a um cenário demonstrado no aplicativo de exemplo:

Pages/BufferedSingleFileUploadDb.cshtml

Pages/BufferedSingleFileUploadDb.cshtml.cs

2 Aviso

Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois


isso pode afetar negativamente o desempenho.

Não confie ou confie na FileName propriedade de IFormFile sem validação. A


FileName propriedade só deve ser usada para fins de exibição e somente após a
codificação HTML.

Os exemplos fornecidos não levam em conta as considerações de segurança.


Informações adicionais são fornecidas pelas seções a seguir e pelo aplicativo de
exemplo :

Considerações sobre segurança


Validação

Carregar arquivos grandes com streaming


O exemplo 3.1 demonstra como usar JavaScript para transmitir um arquivo para uma
ação do controlador. O token antiforgery do arquivo é gerado usando um atributo de
filtro personalizado e passado para os cabeçalhos HTTP do cliente em vez de no corpo
da solicitação. Como o método de ação processa os dados carregados diretamente, a
associação de modelo de formulário é desabilitada por outro filtro personalizado.
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. Depois que as seções de várias partes são lidas, a ação executa
sua própria associação de modelo.

A resposta inicial da página carrega o formulário e salva um token antiforgery em um


cookie (por meio do GenerateAntiforgeryTokenCookieAttribute atributo ). O atributo usa
o suporte interno de antiforgeria do ASP.NET Core para definir um cookie com um
token de solicitação:

C#

public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute


{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery =
context.HttpContext.RequestServices.GetService<IAntiforgery>();

// Send the request token as a JavaScript-readable cookie


var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);

context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}

public override void OnResultExecuted(ResultExecutedContext context)


{
}
}

O DisableFormValueModelBindingAttribute é usado para desabilitar a associação de


modelo:

C#

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute,
IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}

public void OnResourceExecuted(ResourceExecutedContext context)


{
}
}

No aplicativo de exemplo, GenerateAntiforgeryTokenCookieAttribute e


DisableFormValueModelBindingAttribute são aplicados como filtros para os modelos de
aplicativo de página de /StreamedSingleFileUploadDb e
/StreamedSingleFileUploadPhysical em Startup.ConfigureServices usando Razor
convenções de Páginas:

C#

services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions

.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
});

Como a associação de modelo não lê o formulário, os parâmetros associados do


formulário não são associados (consulta, rota e cabeçalho continuam funcionando). O
método de ação funciona diretamente com a Request propriedade . Um
MultipartReader é usado para ler cada seção. Os dados de chave/valor são
armazenados em um KeyValueAccumulator . Depois que as seções de várias partes são
lidas, o conteúdo do KeyValueAccumulator é usado para associar os dados do formulário
a um tipo de modelo.

O método completo StreamingController.UploadDatabase para streaming para um


banco de dados com EF Core:

C#

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error

return BadRequest(ModelState);
}

// Accumulate the form data key-value pairs in the request


(formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();

var boundary = MultipartRequestHelper.GetBoundary(


MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);

var section = await reader.ReadNextSectionAsync();

while (section != null)


{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);

if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage =
contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);

streamedFileContent =
await FileHelpers.ProcessStreamedFile(section,
contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);

if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error

return BadRequest(ModelState);
}

using (var streamReader = new StreamReader(


section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}

formAccumulator.Append(key, value);

if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error
3).");
// Log error

return BadRequest(ModelState);
}
}
}
}

// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}

// Bind form data to the model


var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);

if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error

return BadRequest(ModelState);
}

// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.

var file = new AppFile()


{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};

_context.File.Add(file);
await _context.SaveChangesAsync();

return Created(nameof(StreamingController), null);


}

MultipartRequestHelper ( Utilities/MultipartRequestHelper.cs ):

C#

using System;
using System.IO;
using Microsoft.Net.Http.Headers;

namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----
WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1
states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType,
int lengthLimit)
{
var boundary =
HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;

if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type
boundary.");
}

if (boundary.Length > lengthLimit)


{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit}
exceeded.");
}

return boundary;
}

public static bool IsMultipartContentType(string contentType)


{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/",
StringComparison.OrdinalIgnoreCase) >= 0;
}

public static bool


HasFormDataContentDisposition(ContentDispositionHeaderValue
contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&&
string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}

public static bool


HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1";
filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
||
!string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}

O método completo StreamingController.UploadPhysical para streaming para um local


físico:

C#

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error

return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();

while (section != null)


{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);

if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error

return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage =
Path.GetRandomFileName();

// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.

var streamedFileContent = await


FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath,
trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);

_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved
to " +
"'{TargetFilePath}' as
{TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}

// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}

return Created(nameof(StreamingController), null);


}

No aplicativo de exemplo, as verificações de validação são tratadas por


FileHelpers.ProcessStreamedFile .

Validação
A classe do aplicativo de FileHelpers exemplo demonstra várias verificações de uploads
de arquivos em buffer IFormFile e transmitidos. Para processar IFormFile uploads de
arquivos em buffer no aplicativo de exemplo, consulte o ProcessFormFile método no
Utilities/FileHelpers.cs arquivo . Para processar arquivos transmitidos, consulte o

ProcessStreamedFile método no mesmo arquivo.

2 Aviso

Os métodos de processamento de validação demonstrados no aplicativo de


exemplo não examinam o conteúdo dos arquivos carregados. Na maioria dos
cenários de produção, uma API de scanner de vírus/malware é usada no arquivo
antes de disponibilizar o arquivo para usuários ou outros sistemas.

Embora o exemplo de tópico forneça um exemplo funcional de técnicas de


validação, não implemente a FileHelpers classe em um aplicativo de produção, a
menos que você:

Entenda completamente a implementação.


Modifique a implementação conforme apropriado para o ambiente e as
especificações do aplicativo.

Nunca implemente indiscriminadamente o código de segurança em um


aplicativo sem atender a esses requisitos.

Validação de conteúdo
Use uma API de verificação de vírus/malware de terceiros no conteúdo carregado.

A verificação de arquivos exige recursos do servidor em cenários de alto volume. Se o


desempenho do processamento de solicitações for diminuído devido à verificação de
arquivos, considere descarregar o trabalho de verificação para um serviço em segundo
plano, possivelmente um serviço em execução em um servidor diferente do servidor do
aplicativo. Normalmente, os arquivos carregados são mantidos em uma área em
quarentena até que o verificador de vírus em segundo plano os verifique. Quando um
arquivo é passado, o arquivo é movido para o local normal de armazenamento de
arquivos. Essas etapas geralmente são executadas em conjunto com um registro de
banco de dados que indica o status de verificação de um arquivo. Usando essa
abordagem, o aplicativo e o servidor de aplicativos permanecem focados em responder
às solicitações.

Validação de extensão de arquivo


A extensão do arquivo carregado deve ser verificada em relação a uma lista de
extensões permitidas. Por exemplo:

C#

private string[] permittedExtensions = { ".txt", ".pdf" };

var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();

if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}

Validação de assinatura de arquivo


A assinatura de um arquivo é determinada pelos primeiros bytes no início de um
arquivo. Esses bytes podem ser usados para indicar se a extensão corresponde ao
conteúdo do arquivo. O aplicativo de exemplo verifica as assinaturas de arquivo para
alguns tipos de arquivo comuns. No exemplo a seguir, a assinatura de arquivo para uma
imagem JPEG é verificada em relação ao arquivo :

C#

private static readonly Dictionary<string, List<byte[]>> _fileSignature =


new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};

using (var reader = new BinaryReader(uploadedFileData))


{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));

return signatures.Any(signature =>


headerBytes.Take(signature.Length).SequenceEqual(signature));
}

Para obter assinaturas de arquivo adicionais, use um banco de dados de assinaturas de


arquivo (resultado da pesquisa do Google) e especificações de arquivo oficiais.
Consultar especificações oficiais de arquivo pode garantir que as assinaturas
selecionadas sejam válidas.

Segurança do nome do arquivo


Nunca use um nome de arquivo fornecido pelo cliente para salvar um arquivo no
armazenamento físico. Crie um nome de arquivo seguro para o arquivo usando
Path.GetRandomFileName ou Path.GetTempFileName para criar um caminho completo
(incluindo o nome do arquivo) para armazenamento temporário.

Razor codifica automaticamente valores de propriedade para exibição. O código a


seguir é seguro para usar:

CSHTML
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}

Fora do Razor, sempre HtmlEncode o conteúdo do nome do arquivo da solicitação de


um usuário.

Muitas implementações devem incluir uma verificação de que o arquivo existe; caso
contrário, o arquivo será substituído por um arquivo com o mesmo nome. Forneça
lógica adicional para atender às especificações do aplicativo.

Validação de tamanho
Limite o tamanho dos arquivos carregados.

No aplicativo de exemplo, o tamanho do arquivo é limitado a 2 MB (indicado em bytes).


O limite é fornecido por meio da Configuração do appsettings.json arquivo:

JSON

{
"FileSizeLimit": 2097152
}

O FileSizeLimit é injetado em PageModel classes:

C#

public class BufferedSingleFileUploadPhysicalModel : PageModel


{
private readonly long _fileSizeLimit;

public BufferedSingleFileUploadPhysicalModel(IConfiguration config)


{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}

...
}

Quando um tamanho de arquivo excede o limite, o arquivo é rejeitado:


C#

if (formFile.Length > _fileSizeLimit)


{
// The file is too large ... discontinue processing the file
}

Corresponder o valor do atributo de nome ao nome do


parâmetro do método POST
Em formuláriosRazor que postam dados ou usam javaScript FormData diretamente, o
nome especificado no elemento do formulário ou FormData deve corresponder ao nome
do parâmetro na ação do controlador.

No exemplo a seguir:

Ao usar um <input> elemento, o name atributo é definido como o valor


battlePlans :

HTML

<input type="file" name="battlePlans" multiple>

Ao usar FormData em JavaScript, o nome é definido como o valor battlePlans :

JavaScript

var formData = new FormData();

for (var file in files) {


formData.append("battlePlans", file, file.name);
}

Use um nome correspondente para o parâmetro do método C# ( battlePlans ):

Para um Razor método de manipulador de páginas do Pages chamado Upload :

C#

public async Task<IActionResult> OnPostUploadAsync(List<IFormFile>


battlePlans)

Para um método de ação do controlador POST do MVC:


C#

public async Task<IActionResult> Post(List<IFormFile> battlePlans)

Configuração de servidor e aplicativo

Limite de comprimento do corpo de várias partes


MultipartBodyLengthLimit define o limite para o comprimento de cada corpo de várias
partes. Seções de formulário que excedem esse limite lançam um InvalidDataException
quando analisado. O padrão é 134.217.728 (128 MB). Personalize o limite usando a
MultipartBodyLengthLimit configuração em Startup.ConfigureServices :

C#

public void ConfigureServices(IServiceCollection services)


{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}

RequestFormLimitsAttribute é usado para definir o MultipartBodyLengthLimit para uma


única página ou ação.

Em um Razor aplicativo Pages, aplique o filtro com uma convenção em


Startup.ConfigureServices :

C#

services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
});
Em um Razor aplicativo Pages ou um aplicativo MVC, aplique o filtro ao modelo de
página ou ao método de ação:

C#

// Set the limit to 256 MB


[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}

Kestrel tamanho máximo do corpo da solicitação


Para aplicativos hospedados pelo Kestrel, o tamanho máximo padrão do corpo da
solicitação é de 30.000.000 bytes, que é de aproximadamente 28,6 MB. Personalize o
limite usando a opção de servidor MaxRequestBodySizeKestrel :

C#

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
})
.UseStartup<Startup>();
});

RequestSizeLimitAttribute é usado para definir MaxRequestBodySize para uma única


página ou ação.

Em um Razor aplicativo Pages, aplique o filtro com uma convenção em


Startup.ConfigureServices :

C#

services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});

Em um Razor aplicativo de páginas ou um aplicativo MVC, aplique o filtro ao método de


ação ou classe de manipulador de página:

C#

// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}

O RequestSizeLimitAttribute também pode ser aplicado usando a @attributeRazor


diretiva :

CSHTML

@attribute [RequestSizeLimitAttribute(52428800)]

Outros Kestrel limites


Outros Kestrel limites podem ser aplicados a aplicativos hospedados por Kestrel:

Número máximo de conexões de cliente


Taxas de dados de solicitação e resposta

IIS
O limite de solicitação padrão ( maxAllowedContentLength ) é de 30.000.000 bytes, que é
de aproximadamente 28,6 MB. Personalize o limite no web.config arquivo. No exemplo
a seguir, o limite é definido como 50 MB (52.428.800 bytes):

XML

<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
A maxAllowedContentLength configuração só se aplica ao IIS. Para obter mais
informações, consulte Limites de solicitação <requestLimits>.

Solucionar 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 quando implantado em um servidor


IIS
O erro a seguir indica que o arquivo carregado excede o comprimento de conteúdo
configurado do servidor:

HTTP 404.13 - Not Found


The request filtering module is configured to deny a request that exceeds
the request content length.

Para obter mais informações, consulte a seção IIS .

Falha de conexão
Um erro de conexão e uma conexão de servidor de redefinição provavelmente indicam
que o arquivo carregado excede Kestrelo tamanho máximo do corpo da solicitação. Para
obter mais informações, consulte a seção tamanho máximo do corpo daKestrel
solicitação. Kestrel os limites de conexão do cliente também podem exigir ajuste.

Exceção de referência nula com IFormFile


Se o controlador estiver aceitando arquivos carregados usando IFormFile , mas o valor
for null , confirme se o formulário HTML está especificando um enctype valor de
multipart/form-data . Se esse atributo não estiver definido no <form> elemento , o

upload de arquivo não ocorrerá e nenhum argumento associado IFormFile será null .
Confirme também que a nomenclatura de upload nos dados do formulário corresponde
à nomenclatura do aplicativo.

O fluxo era muito longo


Os exemplos neste tópico dependem MemoryStream para conter o conteúdo do
arquivo carregado. O limite de tamanho de um MemoryStream é int.MaxValue . Se o
cenário de upload de arquivo do aplicativo exigir a retenção de conteúdo de arquivo
maior que 50 MB, use uma abordagem alternativa que não dependa de uma única
MemoryStream para armazenar o conteúdo de um arquivo carregado.

Recursos adicionais
Esvaziamento da solicitação de conexão HTTP

Unrestricted File Upload (Carregamento de arquivo irrestrito)


Segurança do Azure: quadro de segurança: | de validação de entrada Atenuações
Padrões de design de nuvem do Azure: padrão de chave de valete
SDK da Web do ASP.NET Core
Artigo • 10/01/2023 • 2 minutos para o fim da leitura

Visão geral
Microsoft.NET.Sdk.Web é um SDK de projeto do MSBuild para criar aplicativos ASP.NET
Core. É possível criar um aplicativo ASP.NET Core sem esse SDK, no entanto, o SDK da
Web é:

Adaptado para fornecer uma experiência de primeira classe.


O destino recomendado para a maioria dos usuários.

Use o Web.SDK em um projeto:

XML

<Project Sdk="Microsoft.NET.Sdk.Web">
<!-- omitted for brevity -->
</Project>

Recursos habilitados usando o SDK da Web:

Projetos direcionados ao .NET Core 3.0 ou posterior referenciam implicitamente:


O ASP.NET Core estrutura compartilhada.
Analisadores projetados para criar aplicativos ASP.NET Core.

O SDK da Web importa destinos do MSBuild que permitem o uso de perfis de


publicação e publicação usando WebDeploy.

Propriedades

Propriedade Descrição

DisableImplicitFrameworkReferences Desabilita a referência implícita à


Microsoft.AspNetCore.App estrutura compartilhada.

DisableImplicitAspNetCoreAnalyzers Desabilita a referência implícita a analisadores de ASP.NET


Core.

DisableImplicitComponentsAnalyzers Desabilita a referência implícita aos Razor analisadores de


componentes ao criar Blazor aplicativos (servidor).
dotnet aspnet-codegenerator
Artigo • 08/12/2022 • 6 minutos para o fim da leitura

De Rick Anderson

dotnet aspnet-codegenerator – executa o mecanismo de scaffolding do ASP.NET Core.

dotnet aspnet-codegenerator é necessário somente para fazer scaffold da linha de


comando, não é preciso usar o scaffolding com o Visual Studio.

Instalar e atualizar o aspnet-codegenerator


Instale o SDK do .NET .

dotnet aspnet-codegenerator é uma ferramenta global que deve ser instalada. O

comando a seguir instala a versão estável mais recente da ferramenta dotnet aspnet-
codegenerator :

CLI do .NET

dotnet tool install -g dotnet-aspnet-codegenerator

O comando a seguir atualiza dotnet aspnet-codegenerator para a versão estável mais


recente disponível dos SDKs do .NET Core instalado:

CLI do .NET

dotnet tool update -g dotnet-aspnet-codegenerator

Desinstalar aspnet-codegenerator
Pode ser necessário desinstalar o aspnet-codegenerator para resolver problemas. Por
exemplo, se você instalou uma versão prévia do , desinstale-a aspnet-
codegenerator antes de instalar a versão lançada.

Os seguintes comandos desinstalam a dotnet aspnet-codegenerator ferramenta e


instalam a versão estável mais recente:

CLI do .NET

dotnet tool uninstall -g dotnet-aspnet-codegenerator


dotnet tool install -g dotnet-aspnet-codegenerator
Sinopse

dotnet aspnet-codegenerator [arguments] [-p|--project] [-n|--nuget-package-


dir] [-c|--configuration] [-tfm|--target-framework] [-b|--build-base-path]
[--no-build]
dotnet aspnet-codegenerator [-h|--help]

Descrição
O comando global dotnet aspnet-codegenerator executa o mecanismo de scaffolding e
o gerador de código do ASP.NET Core.

Argumentos
generator

O gerador de código para ser executado. Os geradores a seguir estão disponíveis:

Gerador Operação

área Faz scaffold de uma área

controlador Faz scaffold de um controlador

identidade Andaimes Identity

razorpage Scaffolds Razor Pages

exibição Faz scaffolds de um modo de exibição

Opções
-n|--nuget-package-dir

Especifica o diretório de pacote do NuGet.

-c|--configuration {Debug|Release}

Define a configuração da compilação. O valor padrão é Debug .


-tfm|--target-framework

O Framework destino para usar. Por exemplo, net46 .

-b|--build-base-path

O caminho base da compilação.

-h|--help

Imprime uma ajuda breve para o comando.

--no-build

Não compila o projeto antes da execução. Também define o sinalizador --no-restore


implicitamente.

-p|--project <PATH>

Especifica o caminho do arquivo de projeto a ser executado (nome da pasta ou caminho


completo). Se não é especificado, ele usa como padrão o diretório atual.

Opções de gerador
As seções a seguir detalham as opções disponíveis para os geradores com suporte:

Área
Controller
Identity
Razorpage
Visualizar

Opções de área
Essa ferramenta destina-se aos projetos Web do ASP.NET Core com controladores e
exibições. Ele não se destina a Razor aplicativos pages.

Uso: dotnet aspnet-codegenerator area AreaNameToGenerate

O comando anterior gera as seguintes pastas:

Áreas
AreaNameToGenerate
Controladores
Dados
Modelos
Exibições

Opções de controlador
A tabela a seguir lista as opções para aspnet-codegenerator razorpage e
controller view :

Opção Descrição

--model ou -m Classe de modelo a ser usada.

--dataContext ou -dc A DbContext classe a ser usada ou o nome da classe a ser gerada.

--bootstrapVersion ou Especifica a versão de inicialização. Os valores válidos são 3 ou 4 . O


-b padrão é 4 . Se necessário e se não estiver presente, um diretório
wwwroot será criado e incluirá os arquivos de inicialização da versão
especificada.

-- Faz referência a bibliotecas de script nas exibições geradas. Adiciona


referenceScriptLibraries _ValidationScriptsPartial para Editar e Criar páginas.
ou -scripts

--layout ou -l Página de layout personalizada a ser usada.

--useDefaultLayout ou Usa o layout padrão das exibições.


-udl

--force ou -f Substitui os arquivos existentes.

--relativeFolderPath ou Especifique o caminho da pasta de saída relativa do projeto em que o


-outDir arquivo precisa ser gerado, se não especificado, o arquivo será gerado
na pasta do projeto

--useSqlite ou -sqlite Sinalizador para especificar se DbContext deve usar SQLite em vez de
SQL Server.

A tabela a seguir lista as opções exclusivas para aspnet-codegenerator controller :

Opção Descrição

--controllerName O nome do controlador.


ou -name

-- Gera ações do controlador assíncrono.


useAsyncActions
ou -async
Opção Descrição

--noViews ou -nv Não gera modos de exibição.

-- Gere um Controlador com REST a API de estilo. É assumido noViews e todas


restWithNoViews as opções relacionadas à visualização são ignoradas.
ou -api

-- Gere o controlador com ações de leitura/gravação sem um modelo.


readWriteActions
ou -actions

Use o switch -h para obter ajuda sobre o comando aspnet-codegenerator controller :

CLI do .NET

dotnet aspnet-codegenerator controller -h

Confira Fazer scaffold do modelo de filme para obter um exemplo de dotnet aspnet-
codegenerator controller .

Razorpage
Razor As páginas podem ser criadas individualmente especificando o nome da nova
página e o modelo a ser usado. Os modelos com suporte são:

Empty

Create
Edit

Delete

Details
List

Por exemplo, o comando a seguir usa o modelo Editar para gerar MyEdit.cshtml e
MyEdit.cshtml.cs :

CLI do .NET

dotnet aspnet-codegenerator razorpage MyEdit Edit -m Movie -dc


RazorPagesMovieContext -outDir Pages/Movies

Normalmente, o modelo e o nome de arquivo gerado não são especificados e os


seguintes modelos são criados:
Create

Edit
Delete

Details
List

A tabela a seguir lista as opções para aspnet-codegenerator razorpage e


controller view :

Opção Descrição

--model ou -m Classe de modelo a ser usada.

--dataContext ou -dc A DbContext classe a ser usada ou o nome da classe a ser gerada.

--bootstrapVersion ou Especifica a versão de inicialização. Os valores válidos são 3 ou 4 . O


-b padrão é 4 . Se necessário e se não estiver presente, um diretório
wwwroot será criado e incluirá os arquivos de inicialização da versão
especificada.

-- Faz referência a bibliotecas de script nas exibições geradas. Adiciona


referenceScriptLibraries _ValidationScriptsPartial para Editar e Criar páginas.
ou -scripts

--layout ou -l Página de layout personalizada a ser usada.

--useDefaultLayout ou Usa o layout padrão das exibições.


-udl

--force ou -f Substitui os arquivos existentes.

--relativeFolderPath ou Especifique o caminho da pasta de saída relativa do projeto em que o


-outDir arquivo precisa ser gerado, se não especificado, o arquivo será gerado
na pasta do projeto

--useSqlite ou -sqlite Sinalizador para especificar se DbContext deve usar SQLite em vez de
SQL Server.

A tabela a seguir lista as opções exclusivas para aspnet-codegenerator razorpage :

Opção Descrição

--namespaceName ou - O nome do namespace a ser usado no PageModel gerado


namespace

--partialView ou -partial Gere uma exibição parcial. As opções de layout -l e - udl são
ignoradas, se isso for especificado.
Opção Descrição

--noPageModel ou -npm Alterne para não gerar uma classe PageModel para o modelo Vazio

Use o switch -h para obter ajuda sobre o comando aspnet-codegenerator razorpage :

CLI do .NET

dotnet aspnet-codegenerator razorpage -h

Confira Fazer scaffold do modelo de filme para obter um exemplo de dotnet aspnet-
codegenerator razorpage .

Visualizar
Os modos de exibição podem ser individualmente estruturados especificando o nome
da exibição e o modelo a ser usado. Os modelos com suporte são:

Empty
Create

Edit
Delete

Details

List

Por exemplo, o comando a seguir usa o modelo Editar para gerar MyEdit.cshtml :

CLI do .NET

dotnet aspnet-codegenerator view MyEdit Edit -m Movie -dc MovieContext -


outDir Views/Movies

A tabela a seguir lista as opções para aspnet-codegenerator razorpage e


controller view :

Opção Descrição

--model ou -m Classe de modelo a ser usada.

--dataContext ou -dc A DbContext classe a ser usada ou o nome da classe a ser gerada.
Opção Descrição

--bootstrapVersion ou Especifica a versão de inicialização. Os valores válidos são 3 ou 4 . O


-b padrão é 4 . Se necessário e se não estiver presente, um diretório
wwwroot será criado e incluirá os arquivos de inicialização da versão
especificada.

-- Faz referência a bibliotecas de script nas exibições geradas. Adiciona


referenceScriptLibraries _ValidationScriptsPartial para Editar e Criar páginas.
ou -scripts

--layout ou -l Página de layout personalizada a ser usada.

--useDefaultLayout ou Usa o layout padrão das exibições.


-udl

--force ou -f Substitui os arquivos existentes.

--relativeFolderPath ou Especifique o caminho da pasta de saída relativa do projeto em que o


-outDir arquivo precisa ser gerado, se não especificado, o arquivo será gerado
na pasta do projeto

--useSqlite ou -sqlite Sinalizador para especificar se DbContext deve usar SQLite em vez de
SQL Server.

A tabela a seguir lista as opções exclusivas para aspnet-codegenerator view :

Opção Descrição

--controllerNamespace Especifique o nome do namespace a ser usado para o controlador


ou -namespace gerado

--partialView ou -partial Gere uma exibição parcial, outras opções de layout (-l e -udl) serão
ignoradas se isso for especificado

Use o switch -h para obter ajuda sobre o comando aspnet-codegenerator view :

CLI do .NET

dotnet aspnet-codegenerator view -h

Identity
Consulte Scaffold Identity
Criar APIs Web com o ASP.NET Core
Artigo • 28/11/2022 • 44 minutos para o fim da leitura

O ASP.NET Core dá suporte à criação de APIs Web usando controladores ou APIs


mínimas. Em uma API Web, os controladores são classes que derivam de ControllerBase.
Este artigo mostra como usar controladores para lidar com solicitações da API Web. Para
obter informações sobre como criar APIs Web sem controladores, confira Tutorial: Criar
uma API Web mínima com o ASP.NET Core.

Classe ControllerBase
Uma API Web baseada em controlador consiste em uma ou mais classes de controlador
que derivam de ControllerBase. O modelo de projeto da API Web fornece um
controlador inicial:

C#

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase

Os controladores de API Web normalmente devem derivar de ControllerBase, não de


Controller. Controller é derivado de ControllerBase e agrega suporte para exibições;
portanto, serve para manipulação de páginas da Web, não para solicitações de API Web.
Se o mesmo controlador precisar dar suporte a exibições e APIs Web, deriva de
Controller .

A classe ControllerBase fornece muitas propriedades e métodos úteis para lidar com
solicitações HTTP. Por exemplo, CreatedAtAction retorna um código de status 201:

C#

[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Pet> Create(Pet pet)
{
pet.Id = _petsInMemoryStore.Any() ?
_petsInMemoryStore.Max(p => p.Id) + 1 : 1;
_petsInMemoryStore.Add(pet);

return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);


}
A tabela a seguir contém exemplos de métodos em ControllerBase .

Método Observações

BadRequest Retorna o código de status 400.

NotFound Retorna o código de status 404.

PhysicalFile Retorna um arquivo.

TryUpdateModelAsync Invoca model binding.

TryValidateModel Invoca validação de modelo.

Confira uma lista com todos os métodos e propriedades disponíveis em ControllerBase.

Atributos
O namespace Microsoft.AspNetCore.Mvc fornece atributos que podem ser usados para
configurar o comportamento de controladores de API Web e dos métodos de ação. O
exemplo a seguir usa atributos para especificar o verbo de ação HTTP compatível e
quaisquer códigos de status HTTP conhecidos que possam ser retornados:

C#

[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Pet> Create(Pet pet)
{
pet.Id = _petsInMemoryStore.Any() ?
_petsInMemoryStore.Max(p => p.Id) + 1 : 1;
_petsInMemoryStore.Add(pet);

return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);


}

Confira mais alguns exemplos de atributos disponíveis.

Atributo Observações

[Route] Especifica o padrão de URL para um controlador ou ação.

[Bind] Especifica o prefixo e as propriedades que serão incluídos no model binding.

[HttpGet] Identifica uma ação que dá suporte ao verbo de ação HTTP GET.

[Consumes] Especifica os tipos de dados aceitos por uma ação.


Atributo Observações

[Produces] Especifica os tipos de dados retornados por uma ação.

Veja uma lista que inclui os atributos disponíveis no namespace


Microsoft.AspNetCore.Mvc.

Atributo ApiController
O atributo [ApiController] pode ser aplicado a uma classe de controlador para permitir
os seguintes comportamentos opinativos específicos da API:

Requisito de roteamento de atributo


Respostas HTTP 400 automáticas
Inferência de parâmetro de origem da associação
Inferência de solicitação de várias partes/dados de formulário
Detalhes do problema dos códigos de status de erro

Atributo em controladores específicos


O atributo [ApiController] pode ser aplicado a controladores específicos, como no
exemplo a seguir do modelo de projeto:

C#

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase

Atributo em vários controladores


Uma abordagem ao uso do atributo em mais de um controlador é a criação de uma
classe de controlador base personalizada anotada com o atributo [ApiController]. O
exemplo a seguir mostra uma classe base personalizada e um controlador derivado
dela:

C#

[ApiController]
public class MyControllerBase : ControllerBase
{
}
C#

[Produces(MediaTypeNames.Application.Json)]
[Route("[controller]")]
public class PetsController : MyControllerBase

Atributo em um assembly
O atributo [ApiController] pode ser aplicado a um assembly. Quando o atributo
[ApiController] é aplicado a um assembly, todos os controladores no assembly têm o

atributo [ApiController] aplicado. Não é possível recusar controladores individuais.


Aplique o atributo de nível de assembly ao arquivo Program.cs :

C#

using Microsoft.AspNetCore.Mvc;
[assembly: ApiController]
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Requisito de roteamento de atributo


O atributo [ApiController] transforma em requisito o roteamento de atributo. Por
exemplo:

C#

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase

As ações são inacessíveis por meio de rotas convencionais definidas por UseEndpoints ,
UseMvc ou UseMvcWithDefaultRoute.
Respostas automáticas do HTTP 400
O atributo [ApiController] faz com que os erros de validação do modelo disparem
automaticamente uma resposta HTTP 400. Consequentemente, o código a seguir se
torna desnecessário em um método de ação:

C#

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

O MVC do ASP.NET Core usa o filtro de ação ModelStateInvalidFilter para fazer a


verificação anterior.

Resposta BadRequest padrão


O corpo da solicitação a seguir é um exemplo do tipo serializado:

JSON

{
"": [
"A non-empty request body is required."
]
}

O tipo de resposta padrão para uma resposta HTTP 400 é ValidationProblemDetails. O


corpo da solicitação a seguir é um exemplo do tipo serializado:

JSON

{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7fb5e16a-4c8f23bbfc974667.",
"errors": {
"": [
"A non-empty request body is required."
]
}
}

O tipo ValidationProblemDetails :
Fornece um formato legível por computador para especificar erros nas respostas
da API Web.
Em conformidade com a especificação RFC 7807 .

Para tornar as respostas automáticas e personalizadas consistentes, chame o método


ValidationProblem em vez de BadRequest. ValidationProblem retorna um objeto
ValidationProblemDetails, bem como a resposta automática.

Registrar respostas de 400 automática


Para registrar 400 respostas automáticas, defina a propriedade delegada
InvalidModelStateResponseFactory para executar o processamento personalizado. Por
padrão, InvalidModelStateResponseFactory usa ProblemDetailsFactory para criar uma
instância de ValidationProblemDetails.

O exemplo a seguir mostra como recuperar uma instância de


ILogger<TCategoryName> para registrar informações sobre uma resposta 400
automática::

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behavior, capture the original delegate to
call later.
var builtInFactory = options.InvalidModelStateResponseFactory;

options.InvalidModelStateResponseFactory = context =>


{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();

// Perform logging here.


// ...

// Invoke the default behavior, which produces a


ValidationProblemDetails
// response.
// To produce a custom response, return a different
implementation of
// IActionResult instead.
return builtInFactory(context);
};
});

var app = builder.Build();


app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Desabilitar resposta de 400 automática


Para desabilitar o comportamento 400 automático, defina a propriedade
SuppressModelStateInvalidFilter como true . Adicione o seguinte código realçado:

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Inferência de parâmetro de origem de


associação
Um atributo de origem de associação define o local no qual o valor do parâmetro de
uma ação é encontrado. Os seguintes atributos da origem da associação existem:
Atributo Origem de associação

[FromBody] Corpo da solicitação

[FromForm] Dados do formulário no corpo da solicitação

[FromHeader] Cabeçalho da solicitação

[FromQuery] Parâmetro de cadeia de caracteres de consulta de solicitação

[FromRoute] Dados de rota da solicitação atual

[FromServices] O serviço de solicitação inserido como um parâmetro de ação

[AsParameters] Parâmetros de método

2 Aviso

Não use [FromRoute] quando os valores puderem conter %2f (ou seja, / ). %2f não
ficará sem escape para / . Use [FromQuery] , se o valor puder conter %2f .

Sem o atributo [ApiController] ou outros atributos de origem da associação, como


[FromQuery] , o runtime 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 em uma ordem definida.

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:

C#

[HttpGet]
public ActionResult<List<Product>> Get(
[FromQuery] bool discontinuedOnly = false)
{
List<Product> products = null;

if (discontinuedOnly)
{
products = _productsInMemoryStore.Where(p =>
p.IsDiscontinued).ToList();
}
else
{
products = _productsInMemoryStore;
}
return products;
}

O atributo [ApiController] aplica regras de inferência para as fontes de dados padrão


dos parâmetros de ação. Essas regras poupam você da necessidade de identificar as
origens de associação manualmente aplicando atributos aos parâmetros de ação. As
regras de inferência da origem de associação se comportam da seguinte maneira:

[FromServices] é inferido para parâmetros de tipo complexos registrados no

Contêiner de DI.
[FromBody] é inferido para parâmetros de tipo complexos não registrados no
Contêiner de DI. Uma exceção à regra de inferência [FromBody] é 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.
[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.

Notas de inferência FromBody


[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 associado ao corpo da solicitação, uma
exceção será lançada. Por exemplo, todas as assinaturas de método de ação a seguir
causam uma exceção:

[FromBody] inferido em ambos, pois são tipos complexos.

C#

[HttpPost]
public IActionResult Action1(Product product, Order order)

O atributo [FromBody] em um, inferido no outro, porque é um tipo complexo.


C#

[HttpPost]
public IActionResult Action2(Product product, [FromBody] Order order)

Atributo [FromBody] em ambos.

C#

[HttpPost]
public IActionResult Action3([FromBody] Product product, [FromBody]
Order order)

Observações de inferência do FromServices


A associação de parâmetros vincula parâmetros por meio de injeção de dependência
quando o tipo é configurado como um serviço. Isso significa que não é necessário
aplicar explicitamente o atributo [FromServices] a um parâmetro. No código a seguir,
ambas as ações retornam a hora:

C#

[Route("[controller]")]
[ApiController]
public class MyController : ControllerBase
{
public ActionResult GetWithAttribute([FromServices] IDateTime dateTime)
=> Ok(dateTime.Now);

[Route("noAttribute")]
public ActionResult Get(IDateTime dateTime) => Ok(dateTime.Now);
}

Em casos raros, a DI automática pode interromper aplicativos que têm um tipo de DI


que também é aceito nos métodos de ação de um controlador de API. Não é comum ter
um tipo na DI e como argumento em uma ação do controlador da API.

Para desabilitar a inferência [FromServices] para um único parâmetro de ação, aplique


o atributo de origem de associação desejado ao parâmetro. Por exemplo, aplique o
atributo [FromBody] a um parâmetro de ação que deve ser vinculado ao corpo da
solicitação.

Para desabilitar a inferência [FromServices] globalmente, defina


DisableImplicitFromServicesParameters como true :
C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
});

var app = builder.Build();

app.MapControllers();

app.Run();

Os tipos são verificados na inicialização do aplicativo com IServiceProviderIsService para


determinar se um argumento em uma ação do controlador de API vem da DI ou de
outras fontes.

O mecanismo para inferir a origem da vinculação dos parâmetros de ação do API


Controller usa as seguintes regras:

Um BindingInfo.BindingSource especificado anteriormente nunca é substituído.


Um parâmetro de tipo complexo, registrado no contêiner DI, é atribuído
BindingSource.Services.
Um parâmetro de tipo complexo, não é registrado no contêiner DI, é atribuído
BindingSource.Body.
Um parâmetro com um nome que aparece como um valor de rota em qualquer
modelo de rota é atribuído BindingSource.Path.
Todos os outros parâmetros são BindingSource.Query.

Desabilitar regras de inferência


Para desabilitar a inferência da origem da associação, defina
SuppressInferBindingSourcesForParameters como true :

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
options.DisableImplicitFromServicesParameters = true;
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Inferência de solicitação de várias partes/dados


de formulário
O atributo [ApiController] aplica uma regra de inferência para parâmetros de ação do
tipo IFormFile e IFormFileCollection. O tipo de conteúdo da solicitação multipart/form-
data é inferido para esses tipos.

Para desabilitar o comportamento padrão, defina a propriedade


SuppressConsumesConstraintForFormFileParameters como true :

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Detalhes do problema dos códigos de status de


erro
O MVC transforma um resultado de erro (um resultado com o código de status 400 ou
superior) em um resultado com ProblemDetails. O tipo ProblemDetails tem base na
especificação RFC 7807 para fornecer detalhes de erro legíveis por computador em
uma resposta HTTP.

Considere o seguinte código em uma ação do controlador:

C#

if (pet == null)
{
return NotFound();
}

O método NotFound produz um código de status HTTP 404 com um corpo


ProblemDetails . Por exemplo:

JSON

{
type: "https://tools.ietf.org/html/rfc7231#section-6.5.4",
title: "Not Found",
status: 404,
traceId: "0HLHLV31KRN83:00000001"
}

Desabilitar a resposta de ProblemDetails


A criação automática de um ProblemDetails para códigos de status de erro fica
desabilitada quando a propriedade SuppressMapClientErrors é definida como true .
Adicione os códigos a seguir:
C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Definir os tipos de conteúdo de solicitação com


suporte com o atributo [Consome]
Por padrão, uma ação dá suporte a todos os tipos de conteúdo de solicitação
disponíveis. Por exemplo, se um aplicativo estiver configurado para dar suporte aos
JSformatadores de entradaON e XML, uma ação dará suporte a vários tipos de
conteúdo, incluindo application/json e application/xml .

O atributo [Consome] permite que uma ação limite os tipos de conteúdo de solicitação
com suporte. Aplique o atributo [Consumes] a uma ação ou controlador, especificando
um ou mais tipos de conteúdo:

C#

[HttpPost]
[Consumes("application/xml")]
public IActionResult CreateProduct(Product product)
No código anterior, a ação CreateProduct especifica o tipo de conteúdo
application/xml . As solicitações roteadas para essa ação devem especificar um
cabeçalho Content-Type de application/xml . Solicitações que não especificam um
cabeçalho Content-Type de application/xml resultam em uma resposta Tipo de Mídia
Sem Suporte 415 .

O atributo [Consumes] também permite que uma ação influencie sua seleção com base
no tipo de conteúdo de uma solicitação de entrada, aplicando uma restrição de tipo.
Considere o seguinte exemplo:

C#

[ApiController]
[Route("api/[controller]")]
public class ConsumesController : ControllerBase
{
[HttpPost]
[Consumes("application/json")]
public IActionResult PostJson(IEnumerable<int> values) =>
Ok(new { Consumes = "application/json", Values = values });

[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult PostForm([FromForm] IEnumerable<int> values) =>
Ok(new { Consumes = "application/x-www-form-urlencoded", Values =
values });
}

No código anterior, ConsumesController está configurado para lidar com solicitações


enviadas para a URL https://localhost:5001/api/Consumes . Ambas as ações do
controlador, PostJson e PostForm , tratam de solicitações POST com a mesma URL. Sem
o atributo [Consumes] que aplica uma restrição de tipo, uma exceção de
correspondência ambígua é lançada.

O atributo [Consumes] é aplicado a ambas as ações. A ação PostJson trata as


solicitações enviadas com um cabeçalho Content-Type de application/json . A ação
PostForm trata as solicitações enviadas com um cabeçalho Content-Type de
application/x-www-form-urlencoded .

Recursos adicionais
Exibir ou baixar o código de exemplo . (Como baixar.)
Tipos de retorno de ação do controlador na API Web do ASP.NET Core
Como lidar com erros em APIs Web do ASP.NET Core
Formatadores personalizados na API Web ASP.NET Core
Formatar dados de resposta na API Web ASP.NET Core
Documentação da API Web ASP.NET Core com o Swagger/OpenAPI
Roteamento para ações do controlador no ASP.NET Core
Usar o Visual Studio de túnel de porta para depurar APIs Web
Criar uma API Web com o ASP.NET Core
Tutorial: criar uma API Web com o
ASP.NET Core
Artigo • 10/01/2023 • 69 minutos para o fim da leitura

Por Rick Anderson e Kirk Larkin

Este tutorial ensina os conceitos básicos da criação de uma API Web usando um banco
de dados.

Visão geral
Este tutorial cria a seguinte API:

API Descrição Corpo da Corpo da resposta


solicitação

GET /api/todoitems Obter todos os itens de Nenhum Matriz de itens de


tarefas pendentes tarefas pendentes

GET Obter um item por ID Nenhum Item de tarefas


/api/todoitems/{id} pendentes

POST Adicionar um novo item Item de tarefas Item de tarefas


/api/todoitems pendentes pendentes

PUT Atualizar um item existente Item de tarefas Nenhum


/api/todoitems/{id} pendentes

DELETE Excluir um item Nenhum Nenhum


/api/todoitems/{id}

O diagrama a seguir mostra o design do aplicativo.


Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um projeto Web


Visual Studio
No menu Arquivo, selecione Novo>Projeto.
Insira API Web na caixa de pesquisa.
Selecione o modelo de API Web ASP.NET Core e selecione Avançar.
Na caixa de diálogo Configurar seu novo projeto, nomeie o projeto TodoApi e
selecione Avançar.
Na caixa de diálogo Informações adicionais:
Confirme se o Framework é .NET 7.0 (ou posterior).
Confirme se a caixa de seleção para Usar controladores (desmarcar para
usar APIs mínimas) está marcada.
Selecione Criar.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Testar o projeto
O modelo de projeto cria uma WeatherForecast API com suporte para Swagger.

Visual Studio

Pressione Ctrl + F5 para execução sem o depurador.

O Visual Studio exibe a seguinte caixa de diálogo quando um projeto ainda não
está configurado para usar o SSL:

Selecione Sim se você confia no certificado SSL do IIS Express.


A seguinte caixa de diálogo é exibida:

Selecione Sim se você concordar com confiar no certificado de desenvolvimento.

Para obter informações sobre como confiar no navegador Firefox, consulte Firefox
SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

O Visual Studio inicia o navegador padrão e navega para https://localhost:


<port>/swagger/index.html , onde <port> é um número de porta escolhido
aleatoriamente.

A página /swagger/index.html swagger é exibida. Selecione


OBTER>Experimente>Executar. A página exibe:

O comando Curl para testar a API WeatherForecast.


A URL para testar a API WeatherForecast.
O código de resposta, o corpo e os cabeçalhos.
Uma caixa de listagem suspensa com tipos de mídia e o valor e o esquema de
exemplo.

Se a página do Swagger não aparecer, consulte este problema do GitHub .

O Swagger é usado para gerar páginas úteis de documentação e ajuda para APIs Web.
Este tutorial se concentra na criação de uma API Web. Para obter mais informações
sobre o Swagger, consulte ASP.NET Core documentação da API Web com
Swagger/OpenAPI.
Copie e cole a URL de Solicitação no navegador: https://localhost:
<port>/weatherforecast

JSON semelhante ao exemplo a seguir é retornado:

JSON

[
{
"date": "2019-07-16T19:04:05.7257911-06:00",
"temperatureC": 52,
"temperatureF": 125,
"summary": "Mild"
},
{
"date": "2019-07-17T19:04:05.7258461-06:00",
"temperatureC": 36,
"temperatureF": 96,
"summary": "Warm"
},
{
"date": "2019-07-18T19:04:05.7258467-06:00",
"temperatureC": 39,
"temperatureF": 102,
"summary": "Cool"
},
{
"date": "2019-07-19T19:04:05.7258471-06:00",
"temperatureC": 10,
"temperatureF": 49,
"summary": "Bracing"
},
{
"date": "2019-07-20T19:04:05.7258474-06:00",
"temperatureC": -1,
"temperatureF": 31,
"summary": "Chilly"
}
]

Adicionar uma classe de modelo


Um modelo é um conjunto de classes que representam os dados gerenciados pelo
aplicativo. O modelo para este aplicativo é a TodoItem classe .

Visual Studio
Em Gerenciador de Soluções, clique com o botão direito do mouse no
projeto. Selecione Adicionar>Nova Pasta. Nomeie a pasta Models .
Clique com o botão direito do mouse na Models pasta e selecione
Adicionar>Classe. Dê à classe o nome TodoItem e selecione Adicionar.
Substitua o código do modelo pelo seguinte:

C#

namespace TodoApi.Models;

public class TodoItem


{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

A propriedade Id funciona como a chave exclusiva em um banco de dados relacional.

As classes de modelo podem ir para qualquer lugar no projeto, mas a Models pasta é
usada por convenção.

Adicionar um contexto de banco de dados


O contexto de banco de dados é a classe principal que coordena a funcionalidade do
Entity Framework para um modelo de dados. Essa classe é criada derivando-a da classe
Microsoft.EntityFrameworkCore.DbContext.

Visual Studio

Adicionar pacotes NuGet


No menu Ferramentas , selecione Gerenciador de Pacotes > NuGet Gerenciar
Pacotes NuGet para solução.
Selecione a guia Procurar e, em seguida, insira
Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa.

Selecione Microsoft.EntityFrameworkCore.InMemory no painel esquerdo.


Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
Adicione o contexto de banco de dados
TodoContext
Clique com o botão direito do mouse na Models pasta e selecione
Adicionar>Classe. Nomeie a classe como TodoContext e clique em Adicionar.

Insira o seguinte código:

C#

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models;

public class TodoContext : DbContext


{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; } = null!;


}

Registrar o contexto do banco de dados


No ASP.NET Core, serviços como o contexto de BD precisam ser registrados no
contêiner de DI (injeção de dependência). O contêiner fornece o serviço aos
controladores.

Atualize Program.cs com o seguinte código realçado:

C#

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();


if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

O código anterior:

Adiciona using diretivas.


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.

Faça scaffold de um controlador


Visual Studio

Clique com o botão direito do mouse na pasta Controllers.

Selecione Adicionar>Novo Item Scaffolded.

Selecione Controlador de API com ações, usando o Entity Framework e, em


seguida, selecione Adicionar.

Na caixa de diálogo Adicionar Controlador de API com ações, usando o


Entity Framework:
Selecione TodoItem (TodoApi.Models) na classe Modelo.
Selecione TodoContext (TodoApi.Models) na classe de contexto Dados.
Selecione Adicionar.

Se a operação de scaffolding falhar, selecione Adicionar para tentar realizar


scaffolding uma segunda vez.

O código gerado:
Marca a classe com o [ApiController] atributo . Esse atributo indica se o
controlador responde às solicitações da API Web. Para obter informações sobre
comportamentos específicos habilitados pelo atributo, consulte Criar APIs Web
com ASP.NET Core.
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.

Os modelos de ASP.NET Core para:

Controladores com exibições incluem [action] no modelo de rota.


Os controladores de API não incluem [action] no modelo de rota.

Quando o [action] token não está no modelo de rota, o nome da ação (nome do
método) não é incluído no ponto de extremidade. Ou seja, o nome do método
associado da ação não é usado na rota correspondente.

Atualizar o método de criação PostTodoItem


Atualize a instrução return no PostTodoItem para usar o operador nameof :

C#

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

// return CreatedAtAction("GetTodoItem", new { id = todoItem.Id },


todoItem);
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id },
todoItem);
}

O código anterior é um HTTP POST método, conforme indicado pelo [HttpPost] atributo .
O método obtém o valor do TodoItem do corpo da solicitação HTTP.

Para obter mais informações, confira Roteamento de atributo com atributos Http[Verb].

O método CreatedAtAction:

Retornará um código de status HTTP 201 se tiver êxito. HTTP 201 é a resposta
padrão para um HTTP POST método que cria um novo recurso no servidor.
Adiciona um cabeçalho de Local à 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 .

Testar PostTodoItem
Pressione CTRL+F5 para executar o aplicativo.

Na janela do navegador Swagger, selecione POST /api/TodoItems e, em seguida,


selecione Experimentar.

Na janela Entrada do corpo da solicitação, atualize o JSativado. Por exemplo,

JSON

{
"name": "walk dog",
"isComplete": true
}

Selecione Executar
Testar o URI do cabeçalho de local
No POST anterior, a interface do usuário do Swagger mostra o cabeçalho de
localização em Cabeçalhos de resposta. Por exemplo, location:
https://localhost:7260/api/TodoItems/1 . O cabeçalho de local mostra o URI para o

recurso criado.

Para testar o cabeçalho de local:


Na janela do navegador Swagger, selecione GET /api/TodoItems/{id}e selecione
Experimentar.

Insira 1 na id caixa de entrada e selecione Executar.


Examine os métodos GET
Dois pontos de extremidade GET são implementados:

GET /api/todoitems
GET /api/todoitems/{id}

A seção anterior mostrou um exemplo da /api/todoitems/{id} rota.

Siga as instruções POST para adicionar outro item de tarefas pendentes e, em seguida,
teste a /api/todoitems rota usando o Swagger.

Este aplicativo usa um banco de dados em memória. Se o aplicativo for interrompido e


iniciado, a solicitação GET anterior não retornará nenhum dado. Se nenhum dado for
retornado, execute POST de dados no aplicativo.

Roteamento e caminhos de URL


O [HttpGet] atributo indica um método que responde a uma solicitação HTTP GET . O
caminho da URL de cada método é construído da seguinte maneira:

Comece com a cadeia de caracteres de modelo no atributo Route do controlador:

C#

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase

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 é TodoItemsController. Portanto, o nome do
controlador é "TodoItems". 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 ao método em seu id parâmetro .

C#

[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 retornados
O tipo de retorno dos GetTodoItems métodos e GetTodoItem é o tipo ActionResult<T>.
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no
corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200
OK , supondo que não haja exceções 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 de status NotFound404 .
Caso contrário, o método retornará 200 com um JScorpo de resposta ON. item
Retornar resulta em uma HTTP 200 resposta.

O método PutTodoItem
Examine o método PutTodoItem :

C#

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}

_context.Entry(todoItem).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return NoContent();
}

PutTodoItem é semelhante a PostTodoItem , exceto que usa HTTP PUT . A resposta é 204

(sem conteúdo) . De acordo com a especificação HTTP, uma PUT solicitação exige que
o cliente envie toda a entidade atualizada, não apenas as alterações. Para dar suporte a
atualizações parciais, use HTTP PATCH.

Testar o método PutTodoItem


Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que
o aplicativo é iniciado. Deverá haver um item no banco de dados antes de você fazer
uma chamada PUT. Chame GET para garantir que haja um item no banco de dados
antes de fazer uma chamada PUT.

Usando a interface do usuário do Swagger, use o botão PUT para atualizar o que tem id
TodoItem = 1 e defina seu nome como "feed fish" . Observe que a resposta é HTTP 204

No Content .
O método DeleteTodoItem
Examine o método DeleteTodoItem :

C#

[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();
}

Testar o método DeleteTodoItem


Use a interface do usuário do Swagger para excluir o que tem id TodoItem = 1. Observe
que a resposta é HTTP 204 No Content .

Testar com http-repl, Postman ou curl


http-repl, Postman e curl geralmente são usados para testar as APIs. O Swagger usa
curl e mostra o curl comando enviado.

Para obter instruções sobre essas ferramentas, consulte os seguintes links:

Testar APIs com o Postman


Instalar e testar APIs com http-repl

Para obter mais informações sobre http-repl , consulte Testar APIs Web com o
HttpRepl.

Impedir o excesso de postagem


Atualmente, o aplicativo de exemplo expõe todo TodoItem o objeto. Os aplicativos de
produção normalmente limitam os dados que são inseridos e retornados usando um
subconjunto do modelo. Há várias razões por trás disso, e a segurança é uma das
principais. O subconjunto de um modelo geralmente é chamado de DTO (Objeto de
Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado
neste tutorial.

Um DTO pode ser usado para:

Impedir o excesso de postagem.


Oculte as propriedades que os clientes não devem exibir.
Omita algumas propriedades para reduzir o tamanho do conteúdo.
Nivele grafos de objeto que contêm objetos aninhados. Grafos de objeto
mesclados podem ser mais convenientes para clientes.

Para demonstrar a abordagem DTO, atualize a TodoItem classe para incluir um campo
secreto:

C#

namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
}

O campo secreto precisa estar oculto deste aplicativo, mas um aplicativo administrativo
pode optar por expô-lo.

Verifique se você pode postar e obter o campo de segredo.

Criar um modelo de DTO:

C#

namespace TodoApi.Models;

public class TodoItemDTO


{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

Atualize o TodoItemsController para usar TodoItemDTO :


C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;

public TodoItemsController(TodoContext context)


{
_context = context;
}

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
}

// GET: api/TodoItems/5
// <snippet_GetByID>
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
return NotFound();
}

return ItemToDTO(todoItem);
}
// </snippet_GetByID>

// PUT: api/TodoItems/5
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Update>
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO
todoDTO)
{
if (id != todoDTO.Id)
{
return BadRequest();
}

var todoItem = await _context.TodoItems.FindAsync(id);


if (todoItem == null)
{
return NotFound();
}

todoItem.Name = todoDTO.Name;
todoItem.IsComplete = todoDTO.IsComplete;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
{
return NotFound();
}

return NoContent();
}
// </snippet_Update>

// POST: api/TodoItems
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Create>
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO
todoDTO)
{
var todoItem = new TodoItem
{
IsComplete = todoDTO.IsComplete,
Name = todoDTO.Name
};

_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
}
// </snippet_Create>

// DELETE: api/TodoItems/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();
}

private bool TodoItemExists(long id)


{
return _context.TodoItems.Any(e => e.Id == id);
}

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>


new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
}

Verifique se você não pode postar ou obter o campo de segredo.

Chamar a API Web com o JavaScript


Consulte Tutorial: Chamar uma API Web ASP.NET Core com JavaScript.

Série de vídeos da API Web


Confira Vídeo: Série do Iniciante para: APIs Web.

Adicionar suporte de autenticação a uma API


Web
Identity ASP.NET Core adiciona a funcionalidade de logon da interface do usuário para
ASP.NET Core aplicativos Web. Para proteger APIs Web e SPAs, use um dos seguintes:

Azure Active Directory


Azure Active Directory B2C (Azure AD B2C)
Servidor Duende Identity
O Duende Identity Server é uma estrutura do OpenID Connect e do OAuth 2.0 para
ASP.NET Core. O Servidor Duende Identity habilita os seguintes recursos de segurança:

AaaS (autenticação como serviço)


SSO (logon único) em vários tipos de aplicativo
Controle de acesso para APIs
Gateway de Federação

) Importante

O Software Duende pode exigir que você pague uma taxa de licença pelo uso de
produção do Duende Identity Server. Para obter mais informações, consulte Migrar
do ASP.NET Core 5.0 para o 6.0.

Para obter mais informações, consulte a documentação do Servidor Duende Identity


(site do Duende Software) .

Publicar no Azure
Para obter informações sobre como implantar no Azure, consulte Início Rápido:
Implantar um aplicativo Web ASP.NET.

Recursos adicionais
Exibir ou baixar o código de exemplo para este tutorial . Consulte como baixar.

Para saber mais, consulte os recursos a seguir:

Criar APIs Web com o ASP.NET Core


Tutorial: Criar uma API Web mínima com ASP.NET Core
Documentação da API Web ASP.NET Core com o Swagger/OpenAPI
RazorPáginas com o Entity Framework Core em ASP.NET Core – Tutorial 1 de 8
Roteamento para ações do controlador no ASP.NET Core
Tipos de retorno de ação do controlador na API Web do ASP.NET Core
Implantar aplicativos ASP.NET Core no Serviço de Aplicativo do Azure
Hospedar e implantar o ASP.NET Core
Criar uma API Web com o ASP.NET Core
Tutorial: Criar uma API Web mínima
com ASP.NET Core
Artigo • 10/01/2023 • 33 minutos para o fim da leitura

Por Rick Anderson e Tom Dykstra

APIs mínimas são arquitetas para criar APIs HTTP com dependências mínimas. Eles são
ideais para microsserviços e aplicativos que desejam incluir apenas os arquivos mínimos,
recursos e dependências em ASP.NET Core.

Este tutorial ensina os conceitos básicos da criação de uma API Web mínima com
ASP.NET Core. Para obter um tutorial sobre como criar um projeto de API Web com
base em controladores que contêm mais recursos, consulte Criar uma API Web. Para
obter uma comparação, consulte Diferenças entre APIs mínimas e APIs com
controladores mais adiante neste tutorial.

Visão geral
Este tutorial cria a seguinte API:

API Descrição Corpo da Corpo da resposta


solicitação

GET /todoitems Obter todos os itens de Nenhum Matriz de itens de


tarefas pendentes tarefas pendentes

GET Obter itens pendentes Nenhum Matriz de itens de


/todoitems/complete concluídos tarefas pendentes

GET Obter um item por ID Nenhum Item de tarefas


/todoitems/{id} pendentes

POST /todoitems Adicionar um novo item Item de tarefas Item de tarefas


pendentes pendentes

PUT Atualizar um item existente Item de tarefas Nenhum


/todoitems/{id} pendentes

DELETE Excluir um item Nenhum Nenhum


/todoitems/{id}

Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um projeto de API Web


Visual Studio

Inicie o Visual Studio 2022 e selecione Criar um novo projeto.

Na caixa de diálogo Criar um novo projeto :


Insira Empty na caixa de pesquisa Pesquisar modelos .
Selecione o modelo ASP.NET Core Vazio e selecione Avançar.
Nomeie o projeto ComoApi e selecione Avançar.

Na caixa de diálogo Informações adicionais:


Selecione .NET 7.0
Desmarcar Não usar instruções de nível superior
Escolha Criar
Examinar o código
O Program.cs arquivo contém o seguinte código:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

O código anterior:

Cria um WebApplicationBuilder e um WebApplication com padrões pré-


configurados.
Cria um ponto de extremidade / HTTP GET que retorna Hello World! :

Executar o aplicativo

Visual Studio

Pressione Ctrl + F5 para execução sem o depurador.

O Visual Studio exibe a caixa de diálogo a seguir:

Selecione Sim se você confia no certificado SSL do IIS Express.

A seguinte caixa de diálogo é exibida:


Selecione Sim se você concordar com confiar no certificado de desenvolvimento.

Para obter informações sobre como confiar no navegador Firefox, consulte Firefox
SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

O Visual Studio inicia o Kestrel servidor Web e abre uma janela do navegador.

Hello World! é exibido no navegador. O Program.cs arquivo contém um aplicativo

mínimo, mas completo.

Adicionar pacotes NuGet


Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e ao
diagnóstico usados neste tutorial.

Visual Studio

No menu Ferramentas , selecione Gerenciador de Pacotes > NuGet Gerenciar


Pacotes NuGet para solução.
Selecione a guia Procurar.
Insira Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa e
selecione Microsoft.EntityFrameworkCore.InMemory .
Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
Siga as instruções anteriores para adicionar o
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacote.

As classes de contexto de modelo e banco de


dados
Na pasta do projeto, crie um arquivo chamado Todo.cs com o seguinte código:

C#

class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

O código anterior cria o modelo para este aplicativo. Um modelo é uma classe que
representa os dados que o aplicativo gerencia.

Crie um arquivo chamado TodoDb.cs com o seguinte código:

C#

using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext


{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }

public DbSet<Todo> Todos => Set<Todo>();


}

O código anterior define o contexto do banco de dados, que é a classe principal que
coordena a funcionalidade do Entity Framework para um modelo de dados. Essa classe
deriva da Microsoft.EntityFrameworkCore.DbContext classe .

Adicionar o código de API


Substitua o conteúdo do arquivo Program.cs pelo seguinte código:

C#
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>


await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>


await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}

return Results.NotFound();
});
app.Run();

O código realçado a seguir adiciona o contexto do banco de dados ao contêiner de DI


(injeção de dependência) e permite exibir exceções relacionadas ao banco de dados:

C#

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

O contêiner de DI fornece acesso ao contexto do banco de dados e a outros serviços.

Instalar o Postman para testar o aplicativo


Este tutorial usa o Postman para testar a API.

Instalar o Postman
Inicie o aplicativo Web.
Inicie o Postman.
Desabilite a Verificação do certificado SSL
Para o Postman para Windows, selecioneConfigurações de Arquivo> (guia
Geral), desabilite a verificação do certificado SSL.
Para o Postman para macOS, selecionePreferências do Postman> (guia Geral),
desabilite a verificação do certificado SSL.

2 Aviso

Habilite novamente a verificação de certificado SSL depois de testar o


aplicativo de exemplo.

Dados de postagem de teste


O código a seguir em Program.cs cria um ponto /todoitems de extremidade HTTP POST
que adiciona dados ao banco de dados na memória:

C#

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

Execute o aplicativo. O navegador exibe um erro 404 porque não há mais um / ponto
de extremidade.

Use o ponto de extremidade POST para adicionar dados ao aplicativo:

Crie uma nova solicitação HTTP.

Defina o método HTTP como POST .

Defina o URI como https://localhost:<port>/todoitems . Por exemplo:


https://localhost:5001/todoitems

Selecione a guia Corpo.

Selecione bruto.

Defina o tipo como JSON.

No corpo da solicitação, insira JSON para um item pendente:

JSON

{
"name":"walk dog",
"isComplete":true
}

Selecione Enviar.
Examinar os pontos de extremidade GET
O aplicativo de exemplo implementa vários pontos de extremidade GET chamando
MapGet :

API Descrição Corpo da Corpo da resposta


solicitação

GET /todoitems Obter todos os itens de Nenhum Matriz de itens de tarefas


tarefas pendentes pendentes

GET Obter todos os itens Nenhum Matriz de itens de tarefas


/todoitems/complete pendentes concluídos pendentes

GET Obter um item por ID Nenhum Item de tarefas


/todoitems/{id} pendentes

C#

app.MapGet("/todoitems", async (TodoDb db) =>


await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

Testar os pontos de extremidade GET


Teste o aplicativo chamando os pontos de extremidade de um navegador ou postman.
As etapas a seguir são para o Postman.

Crie uma nova solicitação HTTP.


Defina o método HTTP como GET.
Defina o URI de solicitação como https://localhost:<port>/todoitems . Por
exemplo, https://localhost:5001/todoitems .
Selecione Enviar.

A chamada para GET /todoitems produz uma resposta semelhante à seguinte:

JSON

[
{
"id": 1,
"name": "walk dog",
"isComplete": false
}
]

Defina o URI de solicitação como https://localhost:<port>/todoitems/1 . Por


exemplo, https://localhost:5001/todoitems/1 .
Selecione Enviar.
A resposta é semelhante ao seguinte:

JSON

{
"id": 1,
"name": "walk dog",
"isComplete": false
}
Este aplicativo usa um banco de dados em memória. Se o aplicativo for reiniciado, a
solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, os dados
POST serão retornados para o aplicativo e tentem a solicitação GET novamente.

Valores retornados
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no
corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200
OK , supondo que não haja exceções sem tratamento. As exceções sem tratamento
são convertidas em erros 5xx.

Os tipos de retorno podem representar uma ampla gama de códigos de status HTTP.
Por exemplo, GET /todoitems/{id} pode retornar dois valores de status diferentes:

Se nenhum item corresponder à ID solicitada, o método retornará um código de


erro de status NotFound404 .
Caso contrário, o método retorna 200 com um JScorpo de resposta ON. Retornar
item resulta em uma resposta HTTP 200.

Examinar o ponto de extremidade PUT


O aplicativo de exemplo implementa um único ponto de extremidade PUT usando
MapPut :

C#

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
});

Esse método é semelhante ao MapPost método , exceto que ele usa HTTP PUT. Uma
resposta bem-sucedida retorna 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 ponto de extremidade PUT
Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que
o aplicativo é iniciado. Deverá haver um item no banco de dados antes de você fazer
uma chamada PUT. Chame GET para garantir que haja um item no banco de dados
antes de fazer uma chamada PUT.

Atualize o item de tarefas pendentes que tem ID = 1 e defina seu nome como "feed
fish" :

JSON

{
"id": 1,
"name": "feed fish",
"isComplete": false
}

Examinar e testar o ponto de extremidade


DELETE
O aplicativo de exemplo implementa um único ponto de extremidade DELETE usando
MapDelete :

C#

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}

return Results.NotFound();
});

Use o Postman para excluir um item pendente:

Defina o método como DELETE .


Defina o URI do objeto para excluir (por exemplo
https://localhost:5001/todoitems/1 , ).
Selecione Enviar.
Usar a API do MapGroup
O código do aplicativo de exemplo repete o prefixo de todoitems URL sempre que
configura um ponto de extremidade. As APIs Web geralmente têm grupos de pontos de
extremidade com um prefixo de URL comum e o MapGroup método está disponível
para ajudar a organizar esses grupos. Ele reduz o código repetitivo e permite
personalizar grupos inteiros de pontos de extremidade com uma única chamada para
métodos como RequireAuthorization e WithMetadata.

Substitua o conteúdo de Program.cs pelo seguinte código:

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>


await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>


await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();

return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}

return Results.NotFound();
});

app.Run();

O código anterior tem as seguintes alterações:

Adiciona var todoItems = app.MapGroup("/todoitems"); para configurar o grupo


usando o prefixo /todoitems de URL .
Altera todos os app.Map<HttpVerb> métodos para todoItems.Map<HttpVerb> .
Remove o prefixo /todoitems de URL das chamadas de Map<HttpVerb> método.

Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.

Usar a API TypedResults


Os Map<HttpVerb> métodos podem chamar métodos de manipulador de rotas em vez
de usar lambdas. Para ver um exemplo, atualize Program.cs com o seguinte código:

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Where(t =>
t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return TypedResults.Created($"/todoitems/{todo.Id}", todo);


}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return TypedResults.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}
return TypedResults.NotFound();
}

O Map<HttpVerb> código agora chama métodos em vez de lambdas:

C#

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

Esses métodos retornam objetos que implementam IResult e são definidos por
TypedResults:

C#

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Where(t =>
t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return TypedResults.Created($"/todoitems/{todo.Id}", todo);


}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return TypedResults.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}

return TypedResults.NotFound();
}

Os testes de unidade podem chamar esses métodos e testar se eles retornam o tipo
correto. Por exemplo, se o método for GetAllTodos :

C#

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

O código de teste de unidade pode verificar se um objeto do tipo Ok<Todo[]> é


retornado do método de manipulador. Por exemplo:

C#

public async Task GetAllTodos_ReturnsOkOfTodosResult()


{
// Arrange
var db = CreateDbContext();

// Act
var result = await TodosApi.GetAllTodos(db);

// Assert: Check for the correct returned type


Assert.IsType<Ok<Todo[]>>(result);
}
Impedir o excesso de postagem
Atualmente, o aplicativo de exemplo expõe todo Todo o objeto. Normalmente, os
aplicativos de produção limitam os dados que são de entrada e retornados usando um
subconjunto do modelo. Há várias razões por trás disso e a segurança é uma das
principais. O subconjunto de um modelo geralmente é chamado de DTO (Objeto de
Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado
neste artigo.

Um DTO pode ser usado para:

Impedir o excesso de postagem.


Ocultar propriedades que os clientes não devem exibir.
Omita algumas propriedades para reduzir o tamanho da carga.
Nivelar grafos de objeto que contêm objetos aninhados. Grafos de objeto
mesclados podem ser mais convenientes para os clientes.

Para demonstrar a abordagem de DTO, atualize a Todo classe para incluir um campo
secreto:

C#

public class Todo


{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}

O campo de segredo precisa estar oculto deste aplicativo, mas um aplicativo


administrativo pode optar por expô-lo.

Verifique se você pode postar e obter o campo de segredo.

Crie um arquivo chamado TodoItemDTO.cs com o seguinte código:

C#

public class TodoItemDTO


{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }

public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name,
todoItem.IsComplete);
}

Atualize o código em Program.cs para usar este modelo de DTO:

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

RouteGroupBuilder todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Select(x => new
TodoItemDTO(x)).ToArrayAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)


{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};

db.Todos.Add(todoItem);
await db.SaveChangesAsync();

return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);


}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO,
TodoDb db)
{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return TypedResults.NotFound();

todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}

return TypedResults.NotFound();
}

Verifique se você pode postar e obter todos os campos, exceto o campo de segredo.

Diferenças entre APIs mínimas e APIs com


controladores
APIs mínimas têm:

Suporte diferente para filtros. Para obter mais informações, consulte Filtros em
aplicativos de API mínimos
Não há suporte para associação de modelo, por exemplo: IModelBinderProvider,
IModelBinder. O suporte pode ser adicionado com um shim de associação
personalizado.
Não há suporte para associação de formulários, exceto para IFormFile. Para
obter mais informações, consulte Uploads de arquivo usando IFormFile e
IFormFileCollection.
Não há suporte interno para validação, ou seja, IModelValidator
Não há suporte para partes de aplicativo ou o modelo de aplicativo. Não há como
aplicar ou criar suas próprias convenções.
Sem suporte interno de renderização de exibição. É recomendável usar Razor o
Pages para renderizar exibições.
Sem suporte para JsonPatch
Sem suporte para OData

Próximas etapas

Configurar JSopções de serialização ON


Para obter informações sobre como configurar JSa serialização ON em seus aplicativos
de API Mínima, consulte Configurar JSopções de serialização ON.

Lidar com erros e exceções


A página de exceção do desenvolvedor é habilitada por padrão no ambiente de
desenvolvimento para aplicativos mínimos de API. Para obter informações sobre como
lidar com erros e exceções, consulte Manipular erros em ASP.NET Core APIs Web.

Testar aplicativos mínimos de API


Para obter um exemplo de teste de um aplicativo de API mínimo, consulte este exemplo
do GitHub .

Usar OpenAPI (Swagger)


Para obter informações sobre como usar o OpenAPI com aplicativos de API mínimos,
consulte Suporte a OpenAPI em APIs mínimas.

Publicar no Azure
Para obter informações sobre como implantar no Azure, consulte Início Rápido:
Implantar um aplicativo Web ASP.NET.

Saiba mais
Para obter mais informações sobre aplicativos mínimos de API, consulte Referência
rápida de APIs mínimas.
Tutorial: criar uma API Web com o
ASP.NET Core
Artigo • 10/01/2023 • 69 minutos para o fim da leitura

Por Rick Anderson e Kirk Larkin

Este tutorial ensina os conceitos básicos da criação de uma API Web usando um banco
de dados.

Visão geral
Este tutorial cria a seguinte API:

API Descrição Corpo da Corpo da resposta


solicitação

GET /api/todoitems Obter todos os itens de Nenhum Matriz de itens de


tarefas pendentes tarefas pendentes

GET Obter um item por ID Nenhum Item de tarefas


/api/todoitems/{id} pendentes

POST Adicionar um novo item Item de tarefas Item de tarefas


/api/todoitems pendentes pendentes

PUT Atualizar um item existente Item de tarefas Nenhum


/api/todoitems/{id} pendentes

DELETE Excluir um item Nenhum Nenhum


/api/todoitems/{id}

O diagrama a seguir mostra o design do aplicativo.


Pré-requisitos
Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um projeto Web


Visual Studio
No menu Arquivo, selecione Novo>Projeto.
Insira API Web na caixa de pesquisa.
Selecione o modelo de API Web ASP.NET Core e selecione Avançar.
Na caixa de diálogo Configurar seu novo projeto, nomeie o projeto TodoApi e
selecione Avançar.
Na caixa de diálogo Informações adicionais:
Confirme se o Framework é .NET 7.0 (ou posterior).
Confirme se a caixa de seleção para Usar controladores (desmarcar para
usar APIs mínimas) está marcada.
Selecione Criar.

7 Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os
artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de
pacotes (documentação do NuGet). Confirme as versões corretas de pacote em
NuGet.org .

Testar o projeto
O modelo de projeto cria uma WeatherForecast API com suporte para Swagger.

Visual Studio

Pressione Ctrl + F5 para execução sem o depurador.

O Visual Studio exibe a seguinte caixa de diálogo quando um projeto ainda não
está configurado para usar o SSL:

Selecione Sim se você confia no certificado SSL do IIS Express.


A seguinte caixa de diálogo é exibida:

Selecione Sim se você concordar com confiar no certificado de desenvolvimento.

Para obter informações sobre como confiar no navegador Firefox, consulte Firefox
SEC_ERROR_INADEQUATE_KEY_USAGE erro de certificado.

O Visual Studio inicia o navegador padrão e navega para https://localhost:


<port>/swagger/index.html , onde <port> é um número de porta escolhido
aleatoriamente.

A página /swagger/index.html swagger é exibida. Selecione


OBTER>Experimente>Executar. A página exibe:

O comando Curl para testar a API WeatherForecast.


A URL para testar a API WeatherForecast.
O código de resposta, o corpo e os cabeçalhos.
Uma caixa de listagem suspensa com tipos de mídia e o valor e o esquema de
exemplo.

Se a página do Swagger não aparecer, consulte este problema do GitHub .

O Swagger é usado para gerar páginas úteis de documentação e ajuda para APIs Web.
Este tutorial se concentra na criação de uma API Web. Para obter mais informações
sobre o Swagger, consulte ASP.NET Core documentação da API Web com
Swagger/OpenAPI.
Copie e cole a URL de Solicitação no navegador: https://localhost:
<port>/weatherforecast

JSON semelhante ao exemplo a seguir é retornado:

JSON

[
{
"date": "2019-07-16T19:04:05.7257911-06:00",
"temperatureC": 52,
"temperatureF": 125,
"summary": "Mild"
},
{
"date": "2019-07-17T19:04:05.7258461-06:00",
"temperatureC": 36,
"temperatureF": 96,
"summary": "Warm"
},
{
"date": "2019-07-18T19:04:05.7258467-06:00",
"temperatureC": 39,
"temperatureF": 102,
"summary": "Cool"
},
{
"date": "2019-07-19T19:04:05.7258471-06:00",
"temperatureC": 10,
"temperatureF": 49,
"summary": "Bracing"
},
{
"date": "2019-07-20T19:04:05.7258474-06:00",
"temperatureC": -1,
"temperatureF": 31,
"summary": "Chilly"
}
]

Adicionar uma classe de modelo


Um modelo é um conjunto de classes que representam os dados gerenciados pelo
aplicativo. O modelo para este aplicativo é a TodoItem classe .

Visual Studio
Em Gerenciador de Soluções, clique com o botão direito do mouse no
projeto. Selecione Adicionar>Nova Pasta. Nomeie a pasta Models .
Clique com o botão direito do mouse na Models pasta e selecione
Adicionar>Classe. Dê à classe o nome TodoItem e selecione Adicionar.
Substitua o código do modelo pelo seguinte:

C#

namespace TodoApi.Models;

public class TodoItem


{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

A propriedade Id funciona como a chave exclusiva em um banco de dados relacional.

As classes de modelo podem ir para qualquer lugar no projeto, mas a Models pasta é
usada por convenção.

Adicionar um contexto de banco de dados


O contexto de banco de dados é a classe principal que coordena a funcionalidade do
Entity Framework para um modelo de dados. Essa classe é criada derivando-a da classe
Microsoft.EntityFrameworkCore.DbContext.

Visual Studio

Adicionar pacotes NuGet


No menu Ferramentas , selecione Gerenciador de Pacotes > NuGet Gerenciar
Pacotes NuGet para solução.
Selecione a guia Procurar e, em seguida, insira
Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa.

Selecione Microsoft.EntityFrameworkCore.InMemory no painel esquerdo.


Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
Adicione o contexto de banco de dados
TodoContext
Clique com o botão direito do mouse na Models pasta e selecione
Adicionar>Classe. Nomeie a classe como TodoContext e clique em Adicionar.

Insira o seguinte código:

C#

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models;

public class TodoContext : DbContext


{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; } = null!;


}

Registrar o contexto do banco de dados


No ASP.NET Core, serviços como o contexto de BD precisam ser registrados no
contêiner de DI (injeção de dependência). O contêiner fornece o serviço aos
controladores.

Atualize Program.cs com o seguinte código realçado:

C#

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();


if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

O código anterior:

Adiciona using diretivas.


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.

Faça scaffold de um controlador


Visual Studio

Clique com o botão direito do mouse na pasta Controllers.

Selecione Adicionar>Novo Item Scaffolded.

Selecione Controlador de API com ações, usando o Entity Framework e, em


seguida, selecione Adicionar.

Na caixa de diálogo Adicionar Controlador de API com ações, usando o


Entity Framework:
Selecione TodoItem (TodoApi.Models) na classe Modelo.
Selecione TodoContext (TodoApi.Models) na classe de contexto Dados.
Selecione Adicionar.

Se a operação de scaffolding falhar, selecione Adicionar para tentar realizar


scaffolding uma segunda vez.

O código gerado:
Marca a classe com o [ApiController] atributo . Esse atributo indica se o
controlador responde às solicitações da API Web. Para obter informações sobre
comportamentos específicos habilitados pelo atributo, consulte Criar APIs Web
com ASP.NET Core.
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.

Os modelos de ASP.NET Core para:

Controladores com exibições incluem [action] no modelo de rota.


Os controladores de API não incluem [action] no modelo de rota.

Quando o [action] token não está no modelo de rota, o nome da ação (nome do
método) não é incluído no ponto de extremidade. Ou seja, o nome do método
associado da ação não é usado na rota correspondente.

Atualizar o método de criação PostTodoItem


Atualize a instrução return no PostTodoItem para usar o operador nameof :

C#

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

// return CreatedAtAction("GetTodoItem", new { id = todoItem.Id },


todoItem);
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id },
todoItem);
}

O código anterior é um HTTP POST método, conforme indicado pelo [HttpPost] atributo .
O método obtém o valor do TodoItem do corpo da solicitação HTTP.

Para obter mais informações, confira Roteamento de atributo com atributos Http[Verb].

O método CreatedAtAction:

Retornará um código de status HTTP 201 se tiver êxito. HTTP 201 é a resposta
padrão para um HTTP POST método que cria um novo recurso no servidor.
Adiciona um cabeçalho de Local à 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 .

Testar PostTodoItem
Pressione CTRL+F5 para executar o aplicativo.

Na janela do navegador Swagger, selecione POST /api/TodoItems e, em seguida,


selecione Experimentar.

Na janela Entrada do corpo da solicitação, atualize o JSativado. Por exemplo,

JSON

{
"name": "walk dog",
"isComplete": true
}

Selecione Executar
Testar o URI do cabeçalho de local
No POST anterior, a interface do usuário do Swagger mostra o cabeçalho de
localização em Cabeçalhos de resposta. Por exemplo, location:
https://localhost:7260/api/TodoItems/1 . O cabeçalho de local mostra o URI para o

recurso criado.

Para testar o cabeçalho de local:


Na janela do navegador Swagger, selecione GET /api/TodoItems/{id}e selecione
Experimentar.

Insira 1 na id caixa de entrada e selecione Executar.


Examine os métodos GET
Dois pontos de extremidade GET são implementados:

GET /api/todoitems
GET /api/todoitems/{id}

A seção anterior mostrou um exemplo da /api/todoitems/{id} rota.

Siga as instruções POST para adicionar outro item de tarefas pendentes e, em seguida,
teste a /api/todoitems rota usando o Swagger.

Este aplicativo usa um banco de dados em memória. Se o aplicativo for interrompido e


iniciado, a solicitação GET anterior não retornará nenhum dado. Se nenhum dado for
retornado, execute POST de dados no aplicativo.

Roteamento e caminhos de URL


O [HttpGet] atributo indica um método que responde a uma solicitação HTTP GET . O
caminho da URL de cada método é construído da seguinte maneira:

Comece com a cadeia de caracteres de modelo no atributo Route do controlador:

C#

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase

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 é TodoItemsController. Portanto, o nome do
controlador é "TodoItems". 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 ao método em seu id parâmetro .

C#

[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 retornados
O tipo de retorno dos GetTodoItems métodos e GetTodoItem é o tipo ActionResult<T>.
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no
corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200
OK , supondo que não haja exceções 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 de status NotFound404 .
Caso contrário, o método retornará 200 com um JScorpo de resposta ON. item
Retornar resulta em uma HTTP 200 resposta.

O método PutTodoItem
Examine o método PutTodoItem :

C#

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}

_context.Entry(todoItem).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return NoContent();
}

PutTodoItem é semelhante a PostTodoItem , exceto que usa HTTP PUT . A resposta é 204

(sem conteúdo) . De acordo com a especificação HTTP, uma PUT solicitação exige que
o cliente envie toda a entidade atualizada, não apenas as alterações. Para dar suporte a
atualizações parciais, use HTTP PATCH.

Testar o método PutTodoItem


Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que
o aplicativo é iniciado. Deverá haver um item no banco de dados antes de você fazer
uma chamada PUT. Chame GET para garantir que haja um item no banco de dados
antes de fazer uma chamada PUT.

Usando a interface do usuário do Swagger, use o botão PUT para atualizar o que tem id
TodoItem = 1 e defina seu nome como "feed fish" . Observe que a resposta é HTTP 204

No Content .
O método DeleteTodoItem
Examine o método DeleteTodoItem :

C#

[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();
}

Testar o método DeleteTodoItem


Use a interface do usuário do Swagger para excluir o que tem id TodoItem = 1. Observe
que a resposta é HTTP 204 No Content .

Testar com http-repl, Postman ou curl


http-repl, Postman e curl geralmente são usados para testar as APIs. O Swagger usa
curl e mostra o curl comando enviado.

Para obter instruções sobre essas ferramentas, consulte os seguintes links:

Testar APIs com o Postman


Instalar e testar APIs com http-repl

Para obter mais informações sobre http-repl , consulte Testar APIs Web com o
HttpRepl.

Impedir o excesso de postagem


Atualmente, o aplicativo de exemplo expõe todo TodoItem o objeto. Os aplicativos de
produção normalmente limitam os dados que são inseridos e retornados usando um
subconjunto do modelo. Há várias razões por trás disso, e a segurança é uma das
principais. O subconjunto de um modelo geralmente é chamado de DTO (Objeto de
Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado
neste tutorial.

Um DTO pode ser usado para:

Impedir o excesso de postagem.


Oculte as propriedades que os clientes não devem exibir.
Omita algumas propriedades para reduzir o tamanho do conteúdo.
Nivele grafos de objeto que contêm objetos aninhados. Grafos de objeto
mesclados podem ser mais convenientes para clientes.

Para demonstrar a abordagem DTO, atualize a TodoItem classe para incluir um campo
secreto:

C#

namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
}

O campo secreto precisa estar oculto deste aplicativo, mas um aplicativo administrativo
pode optar por expô-lo.

Verifique se você pode postar e obter o campo de segredo.

Criar um modelo de DTO:

C#

namespace TodoApi.Models;

public class TodoItemDTO


{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

Atualize o TodoItemsController para usar TodoItemDTO :


C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;

public TodoItemsController(TodoContext context)


{
_context = context;
}

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
}

// GET: api/TodoItems/5
// <snippet_GetByID>
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
return NotFound();
}

return ItemToDTO(todoItem);
}
// </snippet_GetByID>

// PUT: api/TodoItems/5
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Update>
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO
todoDTO)
{
if (id != todoDTO.Id)
{
return BadRequest();
}

var todoItem = await _context.TodoItems.FindAsync(id);


if (todoItem == null)
{
return NotFound();
}

todoItem.Name = todoDTO.Name;
todoItem.IsComplete = todoDTO.IsComplete;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
{
return NotFound();
}

return NoContent();
}
// </snippet_Update>

// POST: api/TodoItems
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Create>
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO
todoDTO)
{
var todoItem = new TodoItem
{
IsComplete = todoDTO.IsComplete,
Name = todoDTO.Name
};

_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
}
// </snippet_Create>

// DELETE: api/TodoItems/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();
}

private bool TodoItemExists(long id)


{
return _context.TodoItems.Any(e => e.Id == id);
}

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>


new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
}

Verifique se você não pode postar ou obter o campo de segredo.

Chamar a API Web com o JavaScript


Consulte Tutorial: Chamar uma API Web ASP.NET Core com JavaScript.

Série de vídeos da API Web


Confira Vídeo: Série do Iniciante para: APIs Web.

Adicionar suporte de autenticação a uma API


Web
Identity ASP.NET Core adiciona a funcionalidade de logon da interface do usuário para
ASP.NET Core aplicativos Web. Para proteger APIs Web e SPAs, use um dos seguintes:

Azure Active Directory


Azure Active Directory B2C (Azure AD B2C)
Servidor Duende Identity
O Duende Identity Server é uma estrutura do OpenID Connect e do OAuth 2.0 para
ASP.NET Core. O Servidor Duende Identity habilita os seguintes recursos de segurança:

AaaS (autenticação como serviço)


SSO (logon único) em vários tipos de aplicativo
Controle de acesso para APIs
Gateway de Federação

) Importante

O Software Duende pode exigir que você pague uma taxa de licença pelo uso de
produção do Duende Identity Server. Para obter mais informações, consulte Migrar
do ASP.NET Core 5.0 para o 6.0.

Para obter mais informações, consulte a documentação do Servidor Duende Identity


(site do Duende Software) .

Publicar no Azure
Para obter informações sobre como implantar no Azure, consulte Início Rápido:
Implantar um aplicativo Web ASP.NET.

Recursos adicionais
Exibir ou baixar o código de exemplo para este tutorial . Consulte como baixar.

Para saber mais, consulte os recursos a seguir:

Criar APIs Web com o ASP.NET Core


Tutorial: Criar uma API Web mínima com ASP.NET Core
Documentação da API Web ASP.NET Core com o Swagger/OpenAPI
RazorPáginas com o Entity Framework Core em ASP.NET Core – Tutorial 1 de 8
Roteamento para ações do controlador no ASP.NET Core
Tipos de retorno de ação do controlador na API Web do ASP.NET Core
Implantar aplicativos ASP.NET Core no Serviço de Aplicativo do Azure
Hospedar e implantar o ASP.NET Core
Criar uma API Web com o ASP.NET Core
Criar uma API Web com o ASP.NET Core
e o MongoDB
Artigo • 10/01/2023 • 23 minutos para o fim da leitura

Por Pratik Khandelwal e Scott Addie

Este tutorial cria uma API Web que executa operações CRUD (Criar, Ler, Atualizar e
Excluir) em um banco de dados NoSQL do MongoDB .

Neste tutorial, você aprenderá como:

" Configurar o MongoDB
" Criar um banco de dados do MongoDB
" Definir uma coleção e um esquema do MongoDB
" Executar operações CRUD do MongoDB a partir de uma API Web
" Personalizar JSa serialização ON

Pré-requisitos
MongoDB
MongoDB Shell

Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Configurar o MongoDB
No Windows, o MongoDB é instalado em C:\Arquivos de Programas\MongoDB por
padrão. Adicione C:\Program Files\MongoDB\Server\<version_number>\bin à variável de
Path ambiente. Essa alteração possibilita o acesso ao MongoDB a partir de qualquer

lugar em seu computador de desenvolvimento.

Use o Shell do MongoDB instalado anteriormente nas etapas a seguir para criar um
banco de dados, criar coleções e armazenar documentos. Para obter mais informações
sobre comandos do Shell do MongoDB, consulte mongosh .
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.

Console

mongod --dbpath <data_directory_path>

3. Abra outra instância do shell de comando. Conecte-se ao banco de dados de


testes padrão executando o seguinte comando:

Console

mongosh

4. Execute o seguinte comando em um shell de comando:

Console

use BookStore

Um banco de dados chamado BookStore será criado se ele ainda não existir. 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:

Console

db.createCollection('Books')

O seguinte resultado é exibido:

Console

{ "ok" : 1 }

6. Defina um esquema para a coleção Books e insira dois documentos usando o


seguinte comando:

Console
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" }])

Um resultado semelhante ao seguinte é exibido:

Console

{
"acknowledged" : true,
"insertedIds" : [
ObjectId("61a6058e6c43f32854e51f51"),
ObjectId("61a6058e6c43f32854e51f52")
]
}

7 Observação

Os ObjectId s mostrados no resultado anterior não corresponderão aos


mostrados no shell de comando.

7. Visualize os documentos no banco de dados usando o seguinte comando:

Console

db.Books.find().pretty()

Um resultado semelhante ao seguinte é exibido:

Console

{
"_id" : ObjectId("61a6058e6c43f32854e51f51"),
"Name" : "Design Patterns",
"Price" : 54.93,
"Category" : "Computers",
"Author" : "Ralph Johnson"
}
{
"_id" : ObjectId("61a6058e6c43f32854e51f52"),
"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.

Criar o projeto da API Web do ASP.NET Core


Visual Studio

1. Vá para Arquivo>Novo>Projeto.

2. Selecione o tipo de projeto ASP.NET Core API Web e selecione Avançar.

3. Nomeie o projeto BookStoreApi e selecione Avançar.

4. Selecione a estrutura .NET 6.0 (suporte a longo prazo) e selecione Criar.

5. Na janela Console do Gerenciador de Pacotes, navegue até a raiz do projeto.


Execute o seguinte comando para instalar o driver .NET para MongoDB:

PowerShell

Install-Package MongoDB.Driver

Adicionar um modelo de entidade


1. Adicione um diretório Modelos à raiz do projeto.

2. Adicione uma classe Book ao diretório Modelos com o seguinte código:

C#

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace BookStoreApi.Models;

public class Book


{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }

[BsonElement("Name")]
public string BookName { get; set; } = null!;
public decimal Price { get; set; }

public string Category { get; set; } = null!;

public string Author { get; set; } = null!;


}

Na classe anterior, a Id propriedade é:

Necessário para mapear o objeto CLR (Common Language Runtime) para a


coleção MongoDB.
Anotado com [BsonId] para tornar essa propriedade a chave primária do
documento.
Anotado com [BsonRepresentation(BsonType.ObjectId)] para permitir a
passagem do parâmetro como tipo string em vez de uma estrutura
ObjectId . O Mongo processa a conversão de string para ObjectId .

A BookName propriedade é anotada com o [BsonElement] atributo . O valor do


atributo de Name representa o nome da propriedade da coleção do MongoDB.

Adicionar um modelo de configuração


1. Adicione os seguintes valores de configuração de banco de dados a
appsettings.json :

JSON

{
"BookStoreDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "BookStore",
"BooksCollectionName": "Books"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

2. Adicione uma classe BookStoreDatabaseSettings ao diretório Modelos com o


seguinte código:

C#
namespace BookStoreApi.Models;

public class BookStoreDatabaseSettings


{
public string ConnectionString { get; set; } = null!;

public string DatabaseName { get; set; } = null!;

public string BooksCollectionName { get; set; } = null!;


}

A classe anterior BookStoreDatabaseSettings é usada para armazenar os


appsettings.json valores de propriedade do BookStoreDatabase arquivo. Os

JSnomes das propriedades ON e C# são nomeados de forma idêntica para facilitar


o processo de mapeamento.

3. Adicione o código realçado a seguir a Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

No código anterior, a instância de configuração à qual a appsettings.json seção


do BookStoreDatabase arquivo é associada é registrada no contêiner DI (Injeção de
Dependência). Por exemplo, a BookStoreDatabaseSettings propriedade do
ConnectionString objeto é preenchida com a BookStoreDatabase:ConnectionString

propriedade em appsettings.json .

4. Adicione o seguinte código à parte superior de Program.cs para resolver a


BookStoreDatabaseSettings referência:

C#

using BookStoreApi.Models;

Adicionar um serviço de operações CRUD


1. Adicione um diretório Serviços à raiz do projeto.

2. Adicione uma classe BooksService ao diretório Serviços com o seguinte código:


C#

using BookStoreApi.Models;
using Microsoft.Extensions.Options;
using MongoDB.Driver;

namespace BookStoreApi.Services;

public class BooksService


{
private readonly IMongoCollection<Book> _booksCollection;

public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);

var mongoDatabase = mongoClient.GetDatabase(


bookStoreDatabaseSettings.Value.DatabaseName);

_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}

public async Task<List<Book>> GetAsync() =>


await _booksCollection.Find(_ => true).ToListAsync();

public async Task<Book?> GetAsync(string id) =>


await _booksCollection.Find(x => x.Id ==
id).FirstOrDefaultAsync();

public async Task CreateAsync(Book newBook) =>


await _booksCollection.InsertOneAsync(newBook);

public async Task UpdateAsync(string id, Book updatedBook) =>


await _booksCollection.ReplaceOneAsync(x => x.Id == id,
updatedBook);

public async Task RemoveAsync(string id) =>


await _booksCollection.DeleteOneAsync(x => x.Id == id);
}

No código anterior, uma BookStoreDatabaseSettings instância é recuperada do DI


por meio de injeção de construtor. Essa técnica fornece acesso aos
appsettings.json valores de configuração que foram adicionados na seção
Adicionar um modelo de configuração .

3. Adicione o código realçado a seguir a Program.cs :

C#
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

builder.Services.AddSingleton<BooksService>();

No código anterior, a classe BooksService é registrada com a DI para dar suporte à


injeção de construtor nas classes consumidoras. O tempo de vida do serviço
singleton é mais apropriado porque BooksService usa uma dependência direta de
MongoClient . De acordo com as diretrizes oficiais de reutilização do Cliente

Mongo , MongoClient deve ser registrado na DI com um tempo de vida de


serviço singleton.

4. Adicione o seguinte código à parte superior de Program.cs para resolver a


BooksService referência:

C#

using BookStoreApi.Services;

A BooksService classe usa os seguintes MongoDB.Driver membros para executar


operações CRUD no 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:

C#

public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);

var mongoDatabase = mongoClient.GetDatabase(


bookStoreDatabaseSettings.Value.DatabaseName);

_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}

IMongoDatabase : representa o banco de dados Mongo para executar


operações. Este tutorial usa o método genérico GetCollection<TDocument>
(collection) na interface para obter acesso aos dados em uma coleção específica.
Execute operações CRUD na coleção depois que esse método for chamado. Na
chamada de método GetCollection<TDocument>(collection) :
collection representa o nome da coleção.

TDocument representa o tipo de objeto CLR armazenado na coleção.

GetCollection<TDocument>(collection) retorna um objeto MongoCollection que


representa a coleção. Neste tutorial, os seguintes métodos são invocados na coleção:

DeleteOneAsync : exclui um único documento que corresponde aos critérios de


pesquisa fornecidos.
Encontrar< TDocument> : retorna todos os documentos na coleção que
correspondem aos critérios de pesquisa fornecidos.
InsertOneAsync : insere o objeto fornecido como um novo documento na
coleção.
ReplaceOneAsync : substitui o único documento que corresponde aos critérios
de pesquisa fornecidos pelo objeto fornecido.

Adicionar um controlador
Adicione uma classe BooksController ao diretório Controladores com o seguinte código:

C#

using BookStoreApi.Models;
using BookStoreApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace BookStoreApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
private readonly BooksService _booksService;

public BooksController(BooksService booksService) =>


_booksService = booksService;

[HttpGet]
public async Task<List<Book>> Get() =>
await _booksService.GetAsync();

[HttpGet("{id:length(24)}")]
public async Task<ActionResult<Book>> Get(string id)
{
var book = await _booksService.GetAsync(id);
if (book is null)
{
return NotFound();
}

return book;
}

[HttpPost]
public async Task<IActionResult> Post(Book newBook)
{
await _booksService.CreateAsync(newBook);

return CreatedAtAction(nameof(Get), new { id = newBook.Id },


newBook);
}

[HttpPut("{id:length(24)}")]
public async Task<IActionResult> Update(string id, Book updatedBook)
{
var book = await _booksService.GetAsync(id);

if (book is null)
{
return NotFound();
}

updatedBook.Id = book.Id;

await _booksService.UpdateAsync(id, updatedBook);

return NoContent();
}

[HttpDelete("{id:length(24)}")]
public async Task<IActionResult> Delete(string id)
{
var book = await _booksService.GetAsync(id);

if (book is null)
{
return NotFound();
}

await _booksService.RemoveAsync(id);

return NoContent();
}
}

O controlador da API Web anterior:

Usa a BooksService classe para executar operações CRUD.


Contém métodos de ação para dar suporte a solicitações GET, POST, PUT e DELETE
HTTP.
Chama o CreatedAtAction no método de ação Create para retornar uma resposta
HTTP 201 . O código de status 201 é a resposta padrão para um método HTTP
POST que cria um recurso no servidor. CreatedAtAction também adiciona um
cabeçalho Location à resposta. O cabeçalho Location especifica o URI do livro
recém-criado.

Testar a API Web


1. Compile e execute o aplicativo.

2. Navegue até https://localhost:<port>/api/books , em que <port> é o número da


porta atribuído automaticamente para o aplicativo, para testar o método de ação
sem Get parâmetros do controlador. Uma JSresposta ON semelhante à seguinte é
exibida:

JSON

[
{
"id": "61a6058e6c43f32854e51f51",
"bookName": "Design Patterns",
"price": 54.93,
"category": "Computers",
"author": "Ralph Johnson"
},
{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}
]

3. Navegue até https://localhost:<port>/api/books/{id here} para testar o método


de ação Get sobrecarregado do controlador. Uma JSresposta ON semelhante à
seguinte é exibida:

JSON

{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}

Configurar JSopções de serialização ON


Há dois detalhes a serem alterados sobre as JSrespostas ON retornadas na seção Testar
a API Web :

O uso de maiúsculas e minúsculas padrão dos nomes da propriedade deve ser


alterado para corresponder ao uso de maiúsculas e minúsculas Pascal dos nomes
de propriedade do objeto CLR.
A propriedade bookName deve ser retornada como Name .

Para cumprir os requisitos anteriores, faça as seguintes alterações:

1. Em Program.cs , encadeie o seguinte código realçado para a chamada de método


AddControllers :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

builder.Services.AddSingleton<BooksService>();

builder.Services.AddControllers()
.AddJsonOptions(
options => options.JsonSerializerOptions.PropertyNamingPolicy =
null);

Com a alteração anterior, os nomes de propriedade na resposta ON serializada


JSda API Web correspondem aos nomes de propriedade correspondentes no tipo
de objeto CLR. Por exemplo, a Book propriedade da Author classe serializa como
Author em vez de author .

2. Em Models/Book.cs , anote a BookName propriedade com o [JsonPropertyName]


atributo :

C#

[BsonElement("Name")]
[JsonPropertyName("Name")]
public string BookName { get; set; } = null!;

O [JsonPropertyName] valor do atributo de Name representa o nome da


propriedade na resposta ON serializada JSda API Web.

3. Adicione o seguinte código à parte superior de Models/Book.cs para resolver a


referência de [JsonProperty] atributo:

C#

using System.Text.Json.Serialization;

4. Repita as etapas definidas na seção Testar a API Web. Observe a diferença nos
JSnomes de propriedade ON.

Adicionar suporte de autenticação a uma API


Web
Identity ASP.NET Core adiciona a funcionalidade de logon da interface do usuário para
ASP.NET Core aplicativos Web. Para proteger APIs Web e SPAs, use um dos seguintes:

Azure Active Directory


Azure Active Directory B2C (Azure AD B2C)
Servidor duende Identity

O Servidor Duende Identity é uma estrutura OpenID Connect e OAuth 2.0 para ASP.NET
Core. O Servidor Duende Identity habilita os seguintes recursos de segurança:

Autenticação como serviço (AaaS)


SSO (logon único) em vários tipos de aplicativo
Controle de acesso para APIs
Gateway de Federação

) Importante

O Software Duende pode exigir que você pague uma taxa de licença pelo uso de
produção do Duende Identity Server. Para obter mais informações, consulte Migrar
do ASP.NET Core 5.0 para o 6.0.

Para obter mais informações, consulte a documentação do Duende Identity Server (site
do Duende Software) .
Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Criar APIs Web com o ASP.NET Core
Tipos de retorno de ação do controlador na API Web do ASP.NET Core
Criar uma API Web com o ASP.NET Core
Documentação da API Web ASP.NET
Core com o Swagger/OpenAPI
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

Por Christoph Nienaber e Rico Suter

O Swagger (OpenAPI) é uma especificação independente de linguagem para descrever


REST APIs. Ele permite que computadores e humanos entendam os recursos de uma
REST API sem acesso direto ao código-fonte. Seus principais objetivos são:

Minimize a quantidade de trabalho necessária para conectar serviços separados.


Reduza o tempo necessário para documentar com precisão um serviço.

As duas principais implementações de OpenAPI para .NET são Swashbuckle e


NSwag , consulte:

Introdução ao Swashbuckle
Introdução com NSwag

OpenAPI vs. Swagger


O projeto Swagger foi doado para a Iniciativa OpenAPI em 2015 e, desde então, foi
chamado de OpenAPI. Ambos os nomes são usados de forma intercambiável. No
entanto, "OpenAPI" refere-se à especificação . "Swagger" refere-se à família de produtos
de software livre e comerciais do SmartBear que funcionam com a Especificação
openAPI. Os produtos de software livre subsequentes, como OpenAPIGenerator ,
também se enquadram no nome da família Swagger, apesar de não terem sido lançados
pelo SmartBear.

Resumindo:

OpenAPI é uma especificação.


O Swagger é uma ferramenta que usa a especificação OpenAPI. Por exemplo,
OpenAPIGenerator e SwaggerUI.

Especificação de OpenAPI ( openapi.json )


A especificação OpenAPI é um documento que descreve os recursos de sua API. O
documento é baseado no XML e nas anotações de atributo dentro dos controladores e
modelos. É a parte principal do fluxo OpenAPI e é usada para impulsionar ferramentas
como SwaggerUI. Por padrão, ele é chamado openapi.json de . Aqui está um exemplo
de uma especificação de OpenAPI, reduzida para fins de brevidade:

JSON

{
"openapi": "3.0.1",
"info": {
"title": "API V1",
"version": "v1"
},
"paths": {
"/api/Todo": {
"get": {
"tags": [
"Todo"
],
"operationId": "ApiTodoGet",
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ToDoItem"
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ToDoItem"
}
}
},
"text/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ToDoItem"
}
}
}
}
}
}
},
"post": {

}
},
"/api/Todo/{id}": {
"get": {

},
"put": {

},
"delete": {

}
}
},
"components": {
"schemas": {
"ToDoItem": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
},
"name": {
"type": "string",
"nullable": true
},
"isCompleted": {
"type": "boolean"
}
},
"additionalProperties": false
}
}
}
}

Swagger UI
A interface do usuário do Swagger oferece uma interface do usuário baseada na Web
que fornece informações sobre o serviço, usando a especificação de OpenAPI gerada. O
Swashbuckle e o NSwag incluem uma versão incorporada da interface do usuário do
Swagger, para que ele possa ser hospedado em seu aplicativo ASP.NET Core usando
uma chamada de registro de middleware. A interface do usuário da Web tem esta
aparência:
Todo método de ação pública nos controladores pode ser testado da interface do
usuário. Selecione um nome de método para expandir a seção. Adicione os parâmetros
necessários e selecione Experimentar!.
7 Observação

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
Artigo • 28/11/2022 • 22 minutos para o fim da leitura

O Swashbuckle tem três componentes principais:

Swashbuckle.AspNetCore.Swagger : um modelo de objeto swagger e middleware


para expor SwaggerDocument objetos como JSpontos de extremidade ON.

Swashbuckle.AspNetCore.SwaggerGen : um gerador do Swagger cria objetos


SwaggerDocument diretamente de modelos, controladores e rotas. Normalmente,

ele é combinado com o middleware de ponto de extremidade do Swagger para


expor automaticamente o Swagger JSON.

Swashbuckle.AspNetCore.SwaggerUI : uma versão incorporada da ferramenta de


interface do usuário do Swagger. Ele interpreta o Swagger JSON para criar uma
experiência rica e personalizável para descrever a funcionalidade da API Web. Ele
inclui os agentes de teste internos para os métodos públicos.

Instalação do pacote
O Swashbuckle pode ser adicionado com as seguintes abordagens:

Visual Studio

Da janela Console do Gerenciador de Pacotes:

Ir para Exibir> Outro Console doGerenciadorde Pacotes do Windows >

Navegue até o diretório no qual o .csproj arquivo existe

Execute o comando a seguir:

PowerShell

Install-Package Swashbuckle.AspNetCore -Version 6.2.3

Da caixa de diálogo Gerenciar Pacotes NuGet:


Clique com o botão direito do mouse no projeto em pacotes NuGetdo
Gerenciador de Soluções> Manage
Defina a Origem do pacote para "nuget.org"
Verifique se a opção "Incluir pré-lançamento" está habilitada
Insira "Swashbuckle.AspNetCore" na caixa de pesquisa
Selecione o pacote "Swashbuckle.AspNetCore" mais recente na guia
Procurar e clique em Instalar

Adicionar e configurar o middleware do


Swagger
Adicione o gerador swagger à coleção de serviços em Program.cs :

C#

builder.Services.AddControllers();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

Habilite o middleware para servir o documento ON gerado JSe a interface do usuário do


Swagger, também em Program.cs :

C#

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

O código anterior adiciona o middleware do Swagger somente se o ambiente atual


estiver definido como Desenvolvimento. A UseSwaggerUI chamada de método habilita o
Middleware de Arquivo Estático.

Inicie o aplicativo e navegue até https://localhost:<port>/swagger/v1/swagger.json . O


documento gerado que descreve os pontos de extremidade aparece conforme
mostrado na especificação OpenAPI (openapi.json).

A interface do usuário do Swagger pode ser encontrada em https://localhost:


<port>/swagger . Explore a API por meio da interface do usuário do Swagger e incorpore-
a em outros programas.
 Dica

Para atender à interface do usuário do Swagger na raiz do aplicativo


( https://localhost:<port>/ ), defina a propriedade RoutePrefix como uma cadeia
de caracteres vazia:

C#

app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
options.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 /swagger/v1/swagger.json instrui o aplicativo a

procurar o JSarquivo ON na raiz verdadeira da URL (além do prefixo de rota, se usado).


Por exemplo, use https://localhost:<port>/<route_prefix>/swagger/v1/swagger.json ao
invés de https://localhost:
<port>/<virtual_directory>/<route_prefix>/swagger/v1/swagger.json .

7 Observação

Por padrão, o Swashbuckle gera e expõe o Swagger JSON na versão 3.0 da


especificação, chamada oficialmente de Especificação OpenAPI. Para dar suporte à
compatibilidade com versões anteriores, você pode optar por expor JSON no
formato 2.0. Esse formato 2.0 é importante para integrações como o Microsoft
Power Apps e o Microsoft Flow que atualmente dão suporte ao OpenAPI versão
2.0. Para aceitar o formato 2.0, defina a SerializeAsV2 propriedade em Program.cs :

C#

app.UseSwagger(options =>
{
options.SerializeAsV2 = true;
});

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 AddSwaggerGen método adiciona informações
como o autor, a licença e a descrição.

Em Program.cs , importe o seguinte namespace para usar a OpenApiInfo classe:

C#

using Microsoft.OpenApi.Models;

Usando a OpenApiInfo classe, modifique as informações exibidas na interface do


usuário:

C#

builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "ToDo API",
Description = "An ASP.NET Core Web API for managing ToDo items",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "Example Contact",
Url = new Uri("https://example.com/contact")
},
License = new OpenApiLicense
{
Name = "Example License",
Url = new Uri("https://example.com/license")
}
});
});

A interface do usuário do Swagger exibe as informações da versão:


Comentários XML
Comentários XML podem ser habilitados com as seguintes abordagens:

Visual Studio

Clique com o botão direito do mouse no projeto em Gerenciador de Soluções


e selecione Edit <project_name>.csproj .
Adicione GenerateDocumentationFile ao .csproj arquivo:

XML

<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

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:

text

warning CS1591: Missing XML comment for publicly visible type or member
'TodoController'

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# .

XML

<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<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 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 TodoContext a classe. 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.

C#

namespace SwashbuckleSample.Models;

#pragma warning disable CS1591


public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options) :
base(options) { }

public DbSet<TodoItem> TodoItems => Set<TodoItem>();


}
#pragma warning restore CS1591

Configure o Swagger para usar o arquivo XML gerado com as instruções anteriores. 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 TodoApi.XML
arquivo é válido no Windows, mas não no CentOS.

C#

builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "ToDo API",
Description = "An ASP.NET Core Web API for managing ToDo items",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "Example Contact",
Url = new Uri("https://example.com/contact")
},
License = new OpenApiLicense
{
Name = "Example License",
Url = new Uri("https://example.com/license")
}
});

// using System.Reflection;
var xmlFilename = $"
{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory,
xmlFilename));
});

No código anterior, Reflection é usado para criar um nome de arquivo XML


correspondente ao do projeto da API Web. A propriedade AppContext.BaseDirectory é
usada para construir um caminho para o arquivo XML. Alguns recursos do Swagger
(como os esquemas de parâmetros de entrada ou dos métodos HTTP e os códigos de
resposta dos respectivos atributos) funcionam sem o uso de um arquivo de
documentação XML. Para a maioria dos recursos, ou seja, resumos de método e
descrições dos parâmetros e códigos de resposta, é obrigatório o uso de um 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 de
resumo> acima da ação Delete :

C#

/// <summary>
/// Deletes a specific TodoItem.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(long id)
{
var item = await _context.TodoItems.FindAsync(id);

if (item is null)
{
return NotFound();
}

_context.TodoItems.Remove(item);
await _context.SaveChangesAsync();
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 ON gerado JS:

JSON

"delete": {
"tags": [
"Todo"
],
"summary": "Deletes a specific TodoItem.",
"parameters": [
{
"name": "id",
"in": "path",
"description": "",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "Success"
}
}
},

Adicione um <elemento de comentários> à documentação do Create método de ação.


Ele complementa as informações especificadas no elemento <summary> e fornece uma
interface de usuário do Swagger mais robusta. O <remarks> conteúdo do elemento
pode consistir em texto, JSON ou XML.

C#

/// <summary>
/// Creates a TodoItem.
/// </summary>
/// <param name="item"></param>
/// <returns>A newly created TodoItem</returns>
/// <remarks>
/// Sample request:
///
/// POST /Todo
/// {
/// "id": 1,
/// "name": "Item #1",
/// "isComplete": true
/// }
///
/// </remarks>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create(TodoItem item)
{
_context.TodoItems.Add(item);
await _context.SaveChangesAsync();

return CreatedAtAction(nameof(Get), new { id = item.Id }, item);


}

Observe os aprimoramentos da interface do usuário com esses comentários adicionais:

Anotações de dados
Marque o modelo com atributos, encontrados no
System.ComponentModel.DataAnnotations namespace, para ajudar a impulsionar os
componentes da interface do usuário do Swagger.

Adicione o atributo [Required] à propriedade Name da classe TodoItem :

C#

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace SwashbuckleSample.Models;

public class TodoItem


{
public long Id { get; set; }

[Required]
public string Name { get; set; } = null!;

[DefaultValue(false)]
public bool IsComplete { get; set; }
}

A presença desse atributo altera o comportamento da interface do usuário e altera o


esquema ON subjacente JS:

JSON

"schemas": {
"TodoItem": {
"required": [
"name"
],
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"isComplete": {
"type": "boolean",
"default": false
}
},
"additionalProperties": false
}
},
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:

C#

[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class TodoController : ControllerBase
{

A lista suspensa de tipo de mídia seleciona esse tipo de conteúdo como o padrão para
as 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 consomem uma API Web estão mais preocupados com o que é
retornado , especificamente tipos de resposta e códigos de erro (se não 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:

C#

/// <summary>
/// Creates a TodoItem.
/// </summary>
/// <param name="item"></param>
/// <returns>A newly created TodoItem</returns>
/// <remarks>
/// Sample request:
///
/// POST /Todo
/// {
/// "id": 1,
/// "name": "Item #1",
/// "isComplete": true
/// }
///
/// </remarks>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create(TodoItem item)
{
_context.TodoItems.Add(item);
await _context.SaveChangesAsync();

return CreatedAtAction(nameof(Get), new { id = item.Id }, item);


}

A interface do usuário do Swagger agora documenta claramente os códigos de resposta


HTTP esperados:
As convenções podem ser usadas como alternativa para decorar explicitamente ações
individuais com [ProducesResponseType] . Para obter mais informações, confira Usar
convenções da API Web.

Para dar suporte à [ProducesResponseType] decoração, o pacote


Swashbuckle.AspNetCore.Annotations oferece extensões para habilitar e enriquecer
os metadados de resposta, esquema e parâmetro.

Personalizar a interface do usuário


A interface do usuário padrão é funcional e apresentável. 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.

Habilitar o middleware de arquivos estáticos:

C#

app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapControllers();

Para injetar folhas de estilos CSS adicionais, adicione-as à pasta wwwroot do projeto e
especifique o caminho relativo nas opções de middleware:

C#

app.UseSwaggerUI(options =>
{
options.InjectStylesheet("/swagger-ui/custom.css");
});

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
O Swagger não reconhece comentários ou atributos em registros
Melhorar a experiência do desenvolvedor de uma API com a documentação do
Swagger
Introdução ao NSwag e ao ASP.NET
Core
Artigo • 10/01/2023 • 12 minutos para o fim da leitura

Por Christoph Nienaber , Rico Suter e Dave Brock

Exibir ou baixar código de exemplo (como baixar)

NSwag oferece os seguintes recursos:

A capacidade de utilizar a interface do usuário do Swagger e o gerador do


Swagger.
Recursos flexíveis de geração de código.

Com o NSwag, você não precisa de uma API existente. Você pode usar APIs de terceiros
que incorporam o Swagger e geram uma implementação de cliente. O NSwag permite
acelerar o ciclo de desenvolvimento e adaptar-se facilmente às alterações na API.

Registrar o middleware do NSwag


Registre o middleware do NSwag para:

Gerar a especificação do Swagger para a API Web implementada.


Oferecer a interface do usuário do Swagger para navegar e testar a API Web.

Para usar o middleware ASP.NET Core do NSwag , instale o pacote do NuGet


NSwag.AspNetCore . Este pacote contém o middleware para gerar e oferecer a
especificação do Swagger, interface do usuário do Swagger (v2 e v3) e a interface do
usuário do ReDoc .

Use uma das seguintes abordagens para instalar o pacote do NuGet do NSwag:

Visual Studio

Da janela Console do Gerenciador de Pacotes:

Acesse Exibir> OutroConsole do Gerenciador de Pacotes doWindows>

Navegue até o diretório no qual o TodoApi.csproj arquivo existe

Execute o comando a seguir:


PowerShell

Install-Package NSwag.AspNetCore

Da caixa de diálogo Gerenciar Pacotes NuGet:


Clique com o botão direito do mouse no projeto em Gerenciador de
Soluções>Gerenciar Pacotes NuGet
Defina a Origem do pacote para "nuget.org"
Insira "NSwag.AspNetCore" na caixa de pesquisa
Selecione o pacote "NSwag.AspNetCore" na guia Procurar e clique em
Instalar

Adicionar e configurar o middleware do


Swagger
Realize estas etapas para adicionar e configurar o Swagger em seu aplicativo ASP.NET
Core:

No método Startup.ConfigureServices , registre os serviços obrigatórios do


Swagger:

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();

// Register the Swagger services


services.AddSwaggerDocument();
}

No método Startup.Configure , habilite o middleware para atender à especificação


do Swagger gerado e à interface do usuário do Swagger:

C#

public void Configure(IApplicationBuilder app)


{
app.UseStaticFiles();

// Register the Swagger generator and the Swagger UI middlewares


app.UseOpenApi();
app.UseSwaggerUi3();

app.UseMvc();
}

Iniciar o aplicativo. Navegue até:


http://localhost:<port>/swagger para exibir a interface do usuário do Swagger.
http://localhost:<port>/swagger/v1/swagger.json para exibir a especificação

do Swagger.

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 gerar código


de cliente de 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 .
O Serviço Conectado OpenAPI (Swagger): Um Serviço Conectado do Visual
Studio para gerar código de cliente de API em C# ou TypeScript. Também gera
controladores C# para serviços de OpenAPI com o NSwag.

Gerar o código com NSwagStudio


Instale o NSwagStudio, seguindo as instruções no repositório GitHub do
NSwagStudio . Na página de versão do NSwag, você pode baixar uma versão
xcopy que pode ser iniciada sem privilégios de instalação e administrador.

Inicie o NSwagStudio e insira a URL do swagger.json arquivo 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 JSrepresentação ON da


especificação do Swagger.
Na área Saídas , clique na 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:

C#

//----------------------
// <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

[System.CodeDom.Compiler.GeneratedCode("NSwag", "12.0.9.0 (NJsonSchema


v9.13.10.0 (Newtonsoft.Json v11.0.0.0))")]
public partial class TodoClient
{
private string _baseUrl = "https://localhost:44354";
private System.Net.Http.HttpClient _httpClient;
private System.Lazy<Newtonsoft.Json.JsonSerializerSettings>
_settings;

public TodoClient(System.Net.Http.HttpClient httpClient)


{
_httpClient = httpClient;
_settings = new
System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(() =>
{
var settings = new Newtonsoft.Json.JsonSerializerSettings();
UpdateJsonSerializerSettings(settings);
return settings;
});
}

public string BaseUrl


{
get { return _baseUrl; }
set { _baseUrl = value; }
}

// code omitted for brevity

 Dica

O código do cliente C# é gerado com base em seleções na guia Configurações .


Modifique as configurações para executar tarefas como renomeação de namespace
padrão e geração de método síncrono.

Copie o código C# gerado em um arquivo no projeto do cliente que consumirá a


API.
Inicie o consumo da API Web:

C#

var todoClient = new TodoClient();

// Gets all to-dos from the API


var allTodos = await todoClient.GetAllAsync();

// Create a new TodoItem, and save it via the API.


var createdTodo = await todoClient.CreateAsync(new TodoItem());

// Get a single to-do by ID


var foundTodo = await todoClient.GetByIdAsync(1);

Personalizar a documentação da API


O Swagger fornece opções para documentar o modelo de objeto para facilitar o
consumo da API Web.

Descrição e informações da API


No método Startup.ConfigureServices , uma ação de configuração passada para o
método AddSwaggerDocument adiciona informações como o autor, a licença e a descrição:

C#

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.OpenApiContact
{
Name = "Shayne Boyer",
Email = string.Empty,
Url = "https://twitter.com/spboyer"
};
document.Info.License = new NSwag.OpenApiLicense
{
Name = "Use under LICX",
Url = "https://example.com/license"
};
};
});

A interface do usuário do Swagger exibe as informações da versão:


Comentários XML
Para habilitar os comentários XML, execute as seguintes etapas:

Visual Studio

Clique com o botão direito do mouse no projeto no Gerenciador de Soluções


e selecione Edit <project_name>.csproj .
Adicione manualmente as linhas realçadas ao .csproj arquivo:

XML

<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

Anotações de dados
Como o NSwag usa Reflection e o tipo de retorno recomendado para ações de API Web
é ActionResult<T>, ele só pode inferir o tipo de retorno definido por T . Não é possível
inferir automaticamente outros tipos de retorno possíveis.
Considere o seguinte exemplo:

C#

[HttpPost]
public ActionResult<TodoItem> Create(TodoItem item)
{
_context.TodoItems.Add(item);
_context.SaveChanges();

return CreatedAtRoute("GetTodo", new { id = item.Id }, item);


}

A ação anterior retorna ActionResult<T> . Dentro da ação, ele está retornando


CreatedAtRoute. Como o controlador tem o [ApiController] atributo , uma BadRequest
resposta 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. Marque a ação com os seguintes atributos:

C#

[ProducesResponseType(StatusCodes.Status201Created)] // Created
[ProducesResponseType(StatusCodes.Status400BadRequest)] // 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, confira Usar convenções da 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. Como
recomendação, marque todas as ações com esses atributos.

Para obter diretrizes sobre quais respostas HTTP suas ações de API devem retornar,
consulte RFC 9110: Semântica HTTP (Seção 9.3. Definições de método) .
Referência e instalação do comando da
ferramenta .NET OpenAPI
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

Microsoft.dotnet-openapi é uma Ferramenta Global do .NET Core para gerenciar


referências do OpenAPI em um projeto.

Instalação
Para instalar Microsoft.dotnet-openapi , execute o seguinte comando:

CLI do .NET

dotnet tool install -g Microsoft.dotnet-openapi

Adicionar
Adicionar uma referência OpenAPI usando qualquer um dos comandos nesta página
adiciona um <OpenApiReference /> elemento semelhante ao seguinte ao .csproj
arquivo:

XML

<OpenApiReference Include="openapi.json" />

A referência anterior é necessária para que o aplicativo chame o código do cliente


gerado.

Adicionar Arquivo

Opções

Opção Opção longa Descrição Exemplo


curta
Opção Opção longa Descrição Exemplo
curta

-p -- O projeto no qual operar. dotnet openapi


updateProject add file --
updateProject
.\Ref.csproj
.\OpenAPI.json

-c --code- O gerador de código a ser aplicado à referência. As dotnet openapi


generator opções são NSwagCSharp e NSwagTypeScript . Se -- add file
code-generator não for especificado o padrão de .\OpenApi.json --
ferramentas para NSwagCSharp . code-generator

-H --help Mostra informações da Ajuda dotnet openapi


add file --help

Argumentos

Argumento Descrição Exemplo

source-file A origem da qual criar uma referência. Deve ser um dotnet openapi add file
arquivo OpenAPI. .\OpenAPI.json

Adicionar URL

Opções

Opção Opção longa Descrição Exemplo


curta

-p -- O projeto no qual dotnet openapi add url --updateProject


updateProject operar. .\Ref.csproj https://contoso.com/openapi.json

-o --output-file Onde colocar a cópia dotnet openapi add url


local do arquivo https://contoso.com/openapi.json --output-
OpenAPI. file myclient.json

-c --code- O gerador de código a dotnet openapi add url


generator ser aplicado à https://contoso.com/openapi.json --code-
referência. As opções generator
são NSwagCSharp e
NSwagTypeScript .

-H --help Mostra informações da dotnet openapi add url --help


Ajuda
Argumentos

Argumento Descrição Exemplo

source-URL A origem da qual criar uma referência. dotnet openapi add url
Deve ser uma URL. https://contoso.com/openapi.json

Remover
Remove a referência OpenAPI que corresponde ao nome de arquivo especificado do
.csproj arquivo. Quando a referência OpenAPI for removida, os clientes não serão
gerados. Locais .json e .yaml arquivos são excluídos.

Opções

Opção Opção longa Descrição Exemplo


curta

-p -- O projeto no qual dotnet openapi remove --updateProject


updateProject operar. .\Ref.csproj .\OpenAPI.json

-H --help Mostra informações dotnet openapi remove --help


da Ajuda

Argumentos

Argumento Descrição Exemplo

source-file A origem para a qual remover a dotnet openapi remove


referência. .\OpenAPI.json

Atualizar
Atualiza a versão local de um arquivo que foi baixado usando o conteúdo mais recente
da URL de download.

Opções

Opção Opção longa Descrição Exemplo


curta
Opção Opção longa Descrição Exemplo
curta

-p -- O projeto no atualização dotnet openapi --updateProject


updateProject qual operar. .\Ref.csproj https://contoso.com/openapi.json

-H --help Mostra dotnet openapi refresh --help


informações da
Ajuda

Argumentos

Argumento Descrição Exemplo

source-URL A URL da qual atualizar a atualização do dotnet openapi


referência. https://contoso.com/openapi.json
Tipos de retorno de ação do
controlador na API Web do ASP.NET
Core
Artigo • 28/11/2022 • 15 minutos para o fim da leitura

Exibir ou baixar código de exemplo (como baixar)

ASP.NET Core fornece as seguintes opções para tipos de retorno de ação do


controlador de API Web:

Tipo específico
IActionResult
ActionResult<T>
HttpResults

Este artigo explica quando é mais apropriado usar cada tipo de retorno.

Tipo específico
A ação mais básica retorna um tipo de dados primitivo ou complexo, por exemplo,
string ou um objeto personalizado. Considere a seguinte ação, que retorna uma
coleção de objetos Product personalizados:

C#

[HttpGet]
public Task<List<Product>> Get() =>
_productContext.Products.OrderBy(p => p.Name).ToListAsync();

Sem condições conhecidas para proteger, 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 vários tipos de retorno são possíveis, é comum misturar um ActionResult tipo
de retorno com o tipo de retorno primitivo ou complexo. IActionResult ou
ActionResult<T> são necessários para acomodar esse tipo de ação. Vários exemplos de
vários tipos de retorno são fornecidos neste artigo.

Retornar IEnumerable<T> ou IAsyncEnumerable<T>


ASP.NET Core armazena em buffer o resultado de ações que retornam IEnumerable<T>
antes de escrevê-las na resposta. Considere declarar o tipo de retorno da assinatura de
ação como IAsyncEnumerable<T> para garantir iteração assíncrona. Em última análise, o
modo de iteração baseia-se no tipo concreto subjacente que está sendo retornado e o
formatador selecionado afeta como o resultado é processado:

Ao usar System.Text.Json o formatador, o MVC depende do suporte adicionado


System.Text.Json para transmitir o resultado.
Ao usar Newtonsoft.Json ou com XML-based formatores, o resultado é armazenado
em buffer.

Considere a ação a seguir, que retorna registros de produto com preço de venda como
IEnumerable<Product> :

C#

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
var products = _productContext.Products.OrderBy(p => p.Name).ToList();

foreach (var product in products)


{
if (product.IsOnSale)
{
yield return product;
}
}
}

O IAsyncEnumerable<Product> equivalente da ação anterior é:

C#

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
var products = _productContext.Products.OrderBy(p =>
p.Name).AsAsyncEnumerable();

await foreach (var product in products)


{
if (product.IsOnSale)
{
yield return product;
}
}
}
Tipo IActionResult
O IActionResult tipo de retorno é apropriado quando vários ActionResult tipos de
retorno são possíveis em uma ação. Os tipos ActionResult representam vários códigos
de status HTTP. Qualquer classe não abstrata derivada de ActionResult qualifica como
um tipo de retorno válido. Alguns tipos de retorno comuns nessa categoria são
BadRequestResult (400), NotFoundResult (404) e OkObjectResult (200). Como
alternativa, os métodos de conveniência na ControllerBase classe podem ser usados
para retornar ActionResult tipos de uma ação. Por exemplo, return BadRequest(); é
uma forma abreviada de return new BadRequestResult(); .

Como há vários tipos de retorno e caminhos nesse tipo de ação, o uso liberal do
[ProducesResponseType] atributo é necessário. Esse atributo produz detalhes de
resposta mais descritivos para páginas de ajuda da API Web geradas por ferramentas
como o 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:

C#

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? NotFound() : 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 NotFound método de
conveniência é invocado como abreviação para return new NotFoundResult(); .
Um código de status 200 é retornado com o Product objeto quando o produto
existe. O Ok método de conveniência é invocado como abreviação para return
new OkObjectResult(product); .

Ação assíncrona
Considere a seguinte ação assíncrona em que há dois tipos de retorno possíveis:

C#

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}

_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();

return CreatedAtAction(nameof(GetById_IActionResult), new { id =


product.Id }, product);
}

Na ação anterior:

Um código de status 400 é retornado quando a descrição do produto contém


"Widget XYZ". O BadRequest método de conveniência é invocado como
abreviação para return new BadRequestResult(); .

Um código de status 201 é gerado pelo CreatedAtAction método de conveniência


quando um produto é criado. O código a seguir é uma alternativa à chamada
CreatedAtAction :

C#

return new CreatedAtActionResult(nameof(GetById),


"Products",
new { id = product.Id },
product);

No caminho do código anterior, o Product objeto é fornecido no corpo da


resposta. Um Location cabeçalho de resposta que contém a URL do produto
recém-criado é fornecido.

Por exemplo, o modelo a seguir indica que as solicitações devem incluir as propriedades
Name e Description . A falha ao fornecer Name e Description na solicitação faz com que
a validação do modelo falhe.
C#

public class Product


{
public int Id { get; set; }

[Required]
public string Name { get; set; } = string.Empty;

[Required]
public string Description { get; set; } = string.Empty;

public bool IsOnSale { get; set; }


}

Se o [ApiController] atributo 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.

ActionResult vs IActionResult
A seção a seguir se compara ActionResult a IActionResult

Tipo ActionResult<T>
ASP.NET Core inclui o tipo de retorno ActionResult<T> para ações do controlador de
API Web. Ele permite retornar um tipo derivado ActionResult ou retornar um tipo
específico. ActionResult<T> oferece os seguintes benefícios em relação ao tipo
IActionResult:

A [ProducesResponseType] propriedade do Type atributo pode ser excluída. Por


exemplo, [ProducesResponseType(200, Type = typeof(Product))] é simplificado
para [ProducesResponseType(200)] . O tipo de retorno esperado da ação é inferido
do T in 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

return new ObjectResult(T); que é 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:
C#

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
_repository.GetProducts();

Uma opção para corrigir o código anterior é retornar a


_repository.GetProducts().ToList(); .

A maioria das ações tem um tipo de retorno específico. Condições inesperadas podem
ocorrer durante a execução da ação, caso em que o tipo específico não é retornado. Por
exemplo, o parâmetro de entrada de uma ação pode falhar na validação do modelo.
Nesse caso, é comum retornar o tipo ActionResult adequado, em vez do tipo
específico.

Ação síncrona
Considere uma ação síncrona em que há dois tipos de retorno possíveis:

C#

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? NotFound() : product;
}

Na ação anterior:

Um código de status 404 é retornado quando o produto não existe no banco de


dados.
Um código de status 200 é retornado com o objeto correspondente Product
quando o produto existe.

Ação assíncrona
Considere uma ação assíncrona em que há dois tipos de retorno possíveis:

C#

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product
product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}

_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();

return CreatedAtAction(nameof(GetById_ActionResultOfT), new { id =


product.Id }, product);
}

Na ação anterior:

Um código de status 400 (BadRequest) é retornado pelo runtime ASP.NET Core


quando:
O [ApiController] atributo foi aplicado e a validação do modelo falha.
A descrição do produto contém "Widget XYZ".
Um código de status 201 é gerado pelo CreatedAtAction método quando um
produto é criado. Nesse caminho de código, o Product objeto é fornecido no
corpo da resposta. Um Location cabeçalho de resposta que contém a URL do
produto recém-criado é fornecido.

Tipo HttpResults
Além dos tipos de resultado internos específicos do MVC (IActionResult e
ActionResult<T>), ASP.NET Core inclui os tipos HttpResults que podem ser usados em
APIs mínimas e API Web.

Diferente dos tipos de resultado específicos do MVC, o HttpResults :

São uma implementação de resultados que é processada por uma chamada para
IResult.ExecuteAsync.

Não aproveita os Formatadores configurados. Não aproveitar os formatadores


configurados significa:
Alguns recursos como Content negotiation não estão disponíveis.
A produção Content-Type é decidida pela HttpResults implementação.

Pode HttpResults ser útil ao compartilhar código entre APIs mínimas e API Web.
Tipo IResult
O Microsoft.AspNetCore.Http.HttpResults namespace contém classes que implementam
a IResult interface. A IResult interface define um contrato que representa o resultado
de um ponto de extremidade HTTP. A classe Resultados estáticos é usada para criar
objetos variados IResult que representam diferentes tipos de respostas.

A tabela de resultados internos mostra os auxiliares de resultados comuns.

Considere o seguinte código:

C#

[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? Results.NotFound() : Results.Ok(product);
}

Na ação anterior:

Um código de status 404 é retornado quando o produto não existe no banco de


dados.
Um código de status 200 é retornado com o objeto correspondente Product
quando o produto existe, gerado pelo Results.Ok<T>().

Considere o seguinte código:

C#

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return Results.BadRequest();
}

_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();

var location = Url.Action(nameof(GetById), new { id = product.Id }) ??


$"/{product.Id}";
return Results.Created(location, product);
}

Na ação anterior:

Um código de status 400 é retornado quando:


O [ApiController] atributo foi aplicado e a validação do modelo falha.
A descrição do produto contém "Widget XYZ".
Um código de status 201 é gerado pelo Results.Create método quando um
produto é criado. Neste caminho de código, o Product objeto é fornecido no
corpo da resposta. Um Location cabeçalho de resposta que contém a URL do
produto recém-criado é fornecido.

Tipo T> de resultado<


A classe TypedResults estática retorna a implementação concreta IResult que permite o
uso IResult como tipo de retorno. O uso da implementação concreta IResult oferece
o seguinte benefício sobre o tipo IResult:

Todos os [ProducesResponseType] atributos podem ser excluídos, pois a


HttpResult implementação contribui automaticamente para os metadados do

ponto de extremidade.

Quando vários IResult tipos de retorno são necessários, retornar Result<TResult1,


TResultN> é preferencial em vez de IResult retornar. Result<TResult1, TResultN> O
retorno é preferencial porque os tipos de união genéricos retêm automaticamente os
metadados do ponto de extremidade.

Os Results<TResult1, TResultN> tipos de união implementam operadores de conversão


implícita para que o compilador possa converter automaticamente os tipos
especificados nos argumentos genéricos em uma instância do tipo união. Isso tem o
benefício adicional de fornecer verificação em tempo de compilação de que um
manipulador de rotas retorna apenas os resultados que ele declara que faz. Tentar
retornar um tipo que não é declarado como um dos argumentos genéricos resulta
Results<> em um erro de compilação.

Considere o seguinte código:

C#

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? TypedResults.NotFound() :
TypedResults.Ok(product);
}

Na ação anterior:

Um código de status 404 é retornado quando o produto não existe no banco de


dados.
Um código de status 200 é retornado com o objeto correspondente Product
quando o produto existe, gerado pelo TypedResults.Ok<T>.

C#

[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product
product)
{
if (product.Description.Contains("XYZ Widget"))
{
return TypedResults.BadRequest();
}

_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();

var location = Url.Action(nameof(GetById), new { id = product.Id }) ??


$"/{product.Id}";
return TypedResults.Created(location, product);
}

Na ação anterior:

Um código de status 400 é retornado quando:


O [ApiController] atributo foi aplicado e a validação do modelo falha.
A descrição do produto contém "Widget XYZ".
Um código de status 201 é gerado pelo TypedResults.Create método quando um
produto é criado. Neste caminho de código, o Product objeto é fornecido no
corpo da resposta. Um Location cabeçalho de resposta que contém a URL do
produto recém-criado é fornecido.

Recursos adicionais
Tratar solicitações com controladores no ASP.NET Core MVC
Validação de modelo no ASP.NET Core MVC
Documentação da API Web ASP.NET Core com o Swagger/OpenAPI
JsonPatch na API Web do ASP.NET Core
Artigo • 28/11/2022 • 15 minutos para o fim da leitura

Este artigo explica como lidar com JSsolicitações on patch em uma API Web ASP.NET
Core.

Instalação do pacote
JSO suporte ao Patch on ASP.NET Core API Web é baseado Newtonsoft.Json e requer o
Microsoft.AspNetCore.Mvc.NewtonsoftJson pacote NuGet. Para habilitar JSo suporte
ao Patch ON:

Instale o pacote do NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson .

Chame AddNewtonsoftJson. Por exemplo:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

AddNewtonsoftJson substitui os formatadores de entrada e saída baseados em padrão

System.Text.Json usados para formatar todo oJSconteúdo ON. Esse método de extensão
é compatível com os seguintes métodos de registro de serviço MVC:

AddRazorPages
AddControllersWithViews
AddControllers

Adicionar suporte ao JSPatch ON ao usar


System.Text.Json
O System.Text.Json formatador de entrada baseado não dá suporte JSao PATCH ON.
Para adicionar suporte ao JSPATCH ON usando Newtonsoft.Json , deixando os outros
formatadores de entrada e saída inalterados:

Instale o pacote do NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson .

Atualizar Program.cs :

C#

using JsonPatchSample;
using Microsoft.AspNetCore.Mvc.Formatters;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0,
MyJPIF.GetJsonPatchInputFormatter());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Options;

namespace JsonPatchSample;

public static class MyJPIF


{
public static NewtonsoftJsonPatchInputFormatter
GetJsonPatchInputFormatter()
{
var builder = new ServiceCollection()
.AddLogging()
.AddMvc()
.AddNewtonsoftJson()
.Services.BuildServiceProvider();

return builder
.GetRequiredService<IOptions<MvcOptions>>()
.Value
.InputFormatters
.OfType<NewtonsoftJsonPatchInputFormatter>()
.First();
}
}

O código anterior cria uma instância NewtonsoftJsonPatchInputFormatter e a insere


como a primeira entrada da MvcOptions.InputFormatters coleção. Essa ordem de
registro garante que:

NewtonsoftJsonPatchInputFormatter processa JSsolicitações on patch.


A entrada e os formadores existentes System.Text.Json processam todas as outras
JSsolicitações e respostas ON.

Use o Newtonsoft.Json.JsonConvert.SerializeObject método para serializar um


JsonPatchDocument.

Método de solicitação HTTP PATCH


Os métodos PUT e PATCH são usados para atualizar um recurso existente. A diferença
entre eles é que PUT substitui o recurso inteiro, enquanto PATCH especifica apenas as
alterações.

JSNO Patch
JSON Patch é um formato para especificar atualizações a serem aplicadas a um
recurso. Um JSdocumento do Patch ON tem uma matriz de operações. Cada operação
identifica um tipo específico de alteração. Exemplos dessas alterações incluem a adição
de um elemento de matriz ou a substituição de um valor de propriedade.

Por exemplo, os documentos ON a seguir JSrepresentam um recurso, um JSdocumento


on patch para o recurso e o resultado da aplicação das operações de Patch.

Exemplo de recurso
JSON

{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}

JSExemplo de patch ON
JSON

[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]

No JSON anterior:

A propriedade op indica o tipo de operação.


A propriedade path indica o elemento a ser atualizado.
A propriedade value fornece o novo valor.

Recurso depois do patch


Aqui está o recurso depois de aplicar o documento on patch anterior JS:

JSON

{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}

As alterações feitas pela aplicação de um JSdocumento de Patch ON a um recurso são


atômicas. Se alguma operação na lista falhar, nenhuma operação na lista será aplicada.

Sintaxe de path
A propriedade path de um objeto de operação tem barras entre os níveis. Por
exemplo, "/address/zipCode" .

Índices baseados em zero são usados para especificar os elementos da matriz. O


primeiro elemento da matriz addresses estaria em /addresses/0 . Para add o final de
uma matriz, use um hífen ( - ) em vez de um número de índice: /addresses/- .

Operations
A tabela a seguir mostra as operações com suporte, conforme definido na especificação
deJS Patch ON :

Operação Observações

add Adicione uma propriedade ou elemento de matriz. Para a propriedade existente:


defina o valor.

remove Remova uma propriedade ou elemento de matriz.

replace É o mesmo que remove , seguido por add no mesmo local.

move É o mesmo que remove da origem, seguido por add ao destino usando um valor da
origem.

copy É o mesmo que add ao destino usando um valor da origem.

test Retorna o código de status de êxito se o valor em path é igual ao value fornecido.

JSPatch ON no ASP.NET Core


A implementação ASP.NET Core do JSPatch ON é fornecida no pacote NuGet
Microsoft.AspNetCore.JsonPatch .

Código do método de ação


Em um controlador de API, um método de ação para JSo Patch ON:

É anotado com o atributo HttpPatch .


Aceita um JsonPatchDocument<TModel>, normalmente com [FromBody].
Chama ApplyTo(Object) no documento de patch para aplicar as alterações.

Veja um exemplo:

C#

[HttpPatch]
public IActionResult JsonPatchWithModelState(
[FromBody] JsonPatchDocument<Customer> patchDoc)
{
if (patchDoc != null)
{
var customer = CreateCustomer();

patchDoc.ApplyTo(customer, ModelState);

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

return new ObjectResult(customer);


}
else
{
return BadRequest(ModelState);
}
}

Esse código do aplicativo de exemplo funciona com o seguinte Customer modelo:

C#

namespace JsonPatchSample.Models;

public class Customer


{
public string? CustomerName { get; set; }
public List<Order>? Orders { get; set; }
}
C#

namespace JsonPatchSample.Models;

public class Order


{
public string OrderName { get; set; }
public string OrderType { get; set; }
}

O exemplo de método de ação:

Constrói um Customer .
Aplica o patch.
Retorna o resultado no corpo da resposta.

Em um aplicativo real, o código recuperaria os dados de um repositório, como um


banco de dados, e atualizaria o banco de dados após a aplicação do patch.

Estado do modelo
O exemplo de método de ação anterior chama uma sobrecarga de ApplyTo que utiliza o
estado do modelo como um de seus parâmetros. Com essa opção, você pode receber
mensagens de erro nas respostas. O exemplo a seguir mostra o corpo de uma resposta
400 Solicitação Incorreta para uma operação test :

JSON

{
"Customer": [
"The current value 'John' at path 'customerName' != test value 'Nancy'."
]
}

Objetos dinâmicos
O exemplo de método de ação a seguir mostra como aplicar um patch a um objeto
dinâmico:

C#

[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
dynamic obj = new ExpandoObject();
patch.ApplyTo(obj);

return Ok(obj);
}

A operação add
Se path aponta para um elemento de matriz: insere um novo elemento antes do
especificado por path .
Se path aponta para uma propriedade: define o valor da propriedade.
Se path aponta para um local não existente:
Se o recurso no qual fazer patch é um objeto dinâmico: adiciona uma
propriedade.
Se o recurso no qual fazer patch é um objeto estático: a solicitação falha.

O exemplo de documento de patch a seguir define o valor de CustomerName e adiciona


um objeto Order ao final da matriz Orders .

JSON

[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]

A operação remove
Se path aponta para um elemento de matriz: remove o elemento.
Se path aponta para uma propriedade:
Se o recurso no qual fazer patch é um objeto dinâmico: remove a propriedade.
Se o recurso no qual fazer patch é um objeto estático:
Se a propriedade é anulável: define como nulo.
Se a propriedade não é anulável: define como default<T> .

O documento de patch de exemplo a seguir define CustomerName como nulo e exclui


Orders[0] :

JSON

[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]

A operação replace
Esta operação é funcionalmente a mesma que remove seguida por add .

O seguinte documento de patch de exemplo define o valor e CustomerName substitui


Orders[0] por um novo Order objeto:

JSON

[
{
"op": "replace",
"path": "/customerName",
"value": "Barry"
},
{
"op": "replace",
"path": "/orders/0",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]

A operação move
Se path aponta para um elemento de matriz: copia o elemento from para o local
do elemento path e, em seguida, executa uma operação remove no elemento
from .

Se path aponta para uma propriedade: copia o valor da propriedade from para a
propriedade path , depois executa uma operação remove na propriedade from .
Se path aponta para uma propriedade não existente:
Se o recurso no qual fazer patch é um objeto estático: a solicitação falha.
Se o recurso no qual fazer patch é um objeto dinâmico: copia a propriedade
from para o local indicado por path e, em seguida, executa uma operação
remove na propriedade from .

O seguinte exemplo de documento de patch:

Copia o valor de Orders[0].OrderName para CustomerName .


Define Orders[0].OrderName como nulo.
Move Orders[1] para antes de Orders[0] .

JSON

[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]

A operação copy
Esta operação é funcionalmente a mesma que uma operação move , sem a etapa final
remove .

O seguinte exemplo de documento de patch:

Copia o valor de Orders[0].OrderName para CustomerName .


Insere uma cópia de Orders[1] antes de Orders[0] .

JSON
[
{
"op": "copy",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "copy",
"from": "/orders/1",
"path": "/orders/0"
}
]

A operação test
Se o valor no local indicado por path for diferente do valor fornecido em value , a
solicitação falhará. Nesse caso, toda a solicitação de PATCH falhará, mesmo se todas as
outras operações no documento de patch forem bem-sucedidas.

A operação test normalmente é usada para impedir uma atualização quando há um


conflito de simultaneidade.

O seguinte exemplo de documento de patch não terá nenhum efeito se o valor inicial
de CustomerName for "John", porque o teste falha:

JSON

[
{
"op": "test",
"path": "/customerName",
"value": "Nancy"
},
{
"op": "add",
"path": "/customerName",
"value": "Barry"
}
]

Obter o código
Exibir ou baixar o código de exemplo . (Como baixar.)
Para testar o exemplo, execute o aplicativo e envie solicitações HTTP com as seguintes
configurações:

URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
Método HTTP: PATCH
Cabeçalho: Content-Type: application/json-patch+json
Corpo: copie e cole um dos exemplos de JSdocumento de patch ON da JS pasta de
projeto ON.

Recursos adicionais
Especificação do método PATCH IETF RFC 5789
Especificação do Patch ON do IETF RFC 6902 JSON
Ponteiro RFC 6901 JSON do IETF
JSDocumentação do Patch ON . Inclui links para recursos para criar
JSdocumentos do Patch ON.
JScódigo-fonte ASP.NET Core ON Patch
Formatar dados de resposta na API Web
ASP.NET Core
Artigo • 10/01/2023 • 26 minutos para o fim da leitura

ASP.NET Core MVC dá suporte à formatação de dados de resposta, usando formatos


especificados ou em resposta à solicitação de um cliente.

Resultados de ação específicos do formato


Alguns tipos de resultado de ação são específicos a um formato específico, como
JsonResult e ContentResult. As ações podem retornar resultados que sempre usam um
formato especificado, ignorando a solicitação de um cliente para um formato diferente.
Por exemplo, retornar JsonResult retorna JSdados formatados em ON e retornar
ContentResult retorna dados de cadeia de caracteres formatados em texto sem
formatação.

Uma ação não é necessária para retornar nenhum tipo específico. ASP.NET Core dá
suporte a qualquer valor retornado do objeto. Os resultados de ações que retornam
objetos que não IActionResult são tipos são serializados usando a implementação
apropriada IOutputFormatter . Para obter mais informações, confira Tipos de retorno de
ação do controlador em ASP.NET Core API Web.

Por padrão, o método ControllerBase.Ok auxiliar interno retorna JSdados formatados


em ON:

C#

[HttpGet]
public IActionResult Get()
=> Ok(_todoItemStore.GetList());

O código de exemplo retorna uma lista de itens pendentes. Usar as ferramentas de


desenvolvedor do navegador F12 ou o Postman com o código anterior é exibido:

O cabeçalho de resposta que contém o tipo de conteúdo: application/json;


charset=utf-8 .

Os cabeçalhos de solicitação. Por exemplo, o Accept cabeçalho. O Accept


cabeçalho é ignorado pelo código anterior.

Para retornar dados formatados como texto sem formatação, use ContentResult e o
auxiliar Content:
C#

[HttpGet("Version")]
public ContentResult GetVersion()
=> Content("v1.0.0");

No código anterior, o Content-Type retornado é text/plain .

Para ações com vários tipos de retorno, retorne IActionResult . Por exemplo, ao retornar
diferentes códigos de status HTTP com base no resultado da operação.

Negociação de conteúdo
A negociação de conteúdo ocorre quando o cliente especifica um cabeçalho Accept .
O formato padrão usado pelo ASP.NET Core é JSON . A negociação de conteúdo é:

Implementado por ObjectResult.


Integrado aos resultados da ação específica do código de status retornados dos
métodos auxiliares. Os métodos auxiliares dos resultados da ação são baseados
em ObjectResult .

Quando um tipo de modelo é retornado, o tipo de retorno é ObjectResult .

O seguinte método de ação usa os métodos auxiliares Ok e NotFound :

C#

[HttpGet("{id:long}")]
public IActionResult GetById(long id)
{
var todo = _todoItemStore.GetById(id);

if (todo is null)
{
return NotFound();
}

return Ok(todo);
}

Por padrão, ASP.NET Core dá suporte aos seguintes tipos de mídia:

application/json

text/json

text/plain
Ferramentas como Fiddler ou Postman podem definir o cabeçalho da Accept
solicitação para especificar o formato de retorno. Quando o Accept cabeçalho contém
um tipo compatível com o servidor, esse tipo é retornado. A próxima seção mostra
como adicionar formatadores adicionais.

As ações do controlador podem retornar POCOs (Objetos CLR Antigos Simples).


Quando um POCO é retornado, o runtime cria automaticamente um ObjectResult que
encapsula o objeto. O cliente obtém o objeto serializado formatado. Se o objeto que
está sendo retornado for null , uma 204 No Content resposta será retornada.

O exemplo a seguir retorna um tipo de objeto:

C#

[HttpGet("{id:long}")]
public TodoItem? GetById(long id)
=> _todoItemStore.GetById(id);

No código anterior, uma solicitação para um item todo válido retorna uma 200 OK
resposta. Uma solicitação para um item todo inválido retorna uma 204 No Content
resposta.

O cabeçalho Accept
A negociação de conteúdo ocorre quando um Accept cabeçalho aparece na solicitação.
Quando uma solicitação contém um cabeçalho accept, ASP.NET Core:

Enumera os tipos de mídia no cabeçalho accept na ordem de preferência.


Tenta encontrar um formatador que pode produzir uma resposta em um dos
formatos especificados.

Se nenhum formatador for encontrado que possa atender à solicitação do cliente,


ASP.NET Core:

Retornará 406 Not Acceptable se MvcOptions.ReturnHttpNotAcceptable estiver


definido como true ou -
Tenta localizar o primeiro formatador que pode produzir uma resposta.

Se nenhum formatador estiver configurado para o formato solicitado, o primeiro


formatador que pode formatar o objeto será usado. Se nenhum Accept cabeçalho for
exibido na solicitação:
O primeiro formatador que pode manipular o objeto é usado para serializar a
resposta.
Não há nenhuma negociação acontecendo. O servidor está determinando qual
formato retornar.

Se o cabeçalho Accept contiver */* , o Cabeçalho será ignorado, a menos que


RespectBrowserAcceptHeader seja definido como true em MvcOptions.

Navegadores e negociação de conteúdo


Ao contrário dos clientes de API típicos, os navegadores da Web fornecem Accept
cabeçalhos. Os navegadores da Web especificam muitos formatos, incluindo curingas.
Por padrão, quando a estrutura detecta que a solicitação vem de um navegador:

O Accept cabeçalho é ignorado.


O conteúdo é retornado em JSON, a menos que configurado de outra forma.

Essa abordagem fornece uma experiência mais consistente entre navegadores ao


consumir APIs.

Para configurar um aplicativo para respeitar os cabeçalhos de aceitação do navegador,


defina a RespectBrowserAcceptHeader propriedade como true :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
options.RespectBrowserAcceptHeader = true;
});

Configurar formatadores
Os aplicativos que precisam dar suporte a formatos extras podem adicionar os pacotes
NuGet apropriados e configurar o suporte. Há formatadores separados para entrada e
saída. Formatadores de entrada são usados pela Model Binding. Formatadores de saída
são usados para formatar respostas. Para obter informações sobre como criar um
formatador personalizado, consulte Formatadores personalizados.

Adicionar suporte ao formato XML


Para configurar formatadores XML implementados usando XmlSerializer, chame
AddXmlSerializerFormatters:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.AddXmlSerializerFormatters();

Ao usar o código anterior, os métodos de controlador retornam o formato apropriado


com base no cabeçalho da Accept solicitação.

Configurar System.Text.Json formatadores baseados em


Para configurar recursos para os System.Text.Json formatadores baseados em , use
Microsoft.AspNetCore.Mvc.JsonOptions.JsonSerializerOptions. O código realçado a
seguir configura a formatação PascalCase em vez da formatação camelCase padrão:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});

Para configurar opções de serialização de saída para ações específicas, use JsonResult.
Por exemplo:

C#

[HttpGet]
public IActionResult Get()
=> new JsonResult(
_todoItemStore.GetList(),
new JsonSerializerOptions { PropertyNamingPolicy = null });

Adicionar Newtonsoft.Json suporte ao formato ON


baseado em JS
Os formatadores ON padrão JSusam System.Text.Json . Para usar os
Newtonsoft.Json formatadores baseados em , instale o pacote NuGet e configure-o
Microsoft.AspNetCore.Mvc.NewtonsoftJson em Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.AddNewtonsoftJson();

No código anterior, a chamada para AddNewtonsoftJson configura os seguintes recursos


de API Web, MVC e Razor Páginas para usar Newtonsoft.Json :

Formatadores de entrada e saída que leem e gravam JSON


JsonResult
JSNO Patch
IJsonHelper
TempData

Alguns recursos podem não funcionar bem com System.Text.Json formatadores


baseados em e exigem uma referência aos Newtonsoft.Json formatadores baseados em .
Continue usando os Newtonsoft.Json formatadores baseados em quando o aplicativo:

Usa atributos Newtonsoft.Json . Por exemplo, [JsonProperty] ou [JsonIgnore] .


Personaliza as configurações de serialização.
Depende dos recursos que Newtonsoft.Json fornecem.

Para configurar recursos para os Newtonsoft.Json formatadores baseados em , use


SerializerSettings:

C#

builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new
DefaultContractResolver();
});

Para configurar opções de serialização de saída para ações específicas, use JsonResult .
Por exemplo:

C#
[HttpGet]
public IActionResult GetNewtonsoftJson()
=> new JsonResult(
_todoItemStore.GetList(),
new JsonSerializerSettings { ContractResolver = new
DefaultContractResolver() });

Formato ProblemDetails e ValidationProblemDetails


respostas
O seguinte método de ação chama ControllerBase.Problem para criar uma
ProblemDetails resposta:

C#

[HttpGet("Error")]
public IActionResult GetError()
=> Problem("Something went wrong.");

Uma ProblemDetails resposta é sempre camelCase, mesmo quando o aplicativo define


o formato como PascalCase. ProblemDetails segue RFC 7807 , que especifica letras
minúsculas.

Quando o [ApiController] atributo é aplicado a uma classe de controlador, o controlador


cria uma ValidationProblemDetails resposta quando a Validação de Modelo falha. Essa
resposta inclui um dicionário que usa os nomes de propriedade do modelo como
chaves de erro, inalterado. Por exemplo, o modelo a seguir inclui uma única propriedade
que requer validação:

C#

public class SampleModel


{
[Range(1, 10)]
public int Value { get; set; }
}

Por padrão, a ValidationProblemDetails resposta retornada quando a Value


propriedade é inválida usa uma chave de erro de Value , conforme mostrado no
exemplo a seguir:

C#
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-00000000000000000000000000000000-000000000000000-00",
"errors": {
"Value": [
"The field Value must be between 1 and 10."
]
}
}

Para formatar os nomes de propriedade usados como chaves de erro, adicione uma
implementação de IMetadataDetailsProvider à
MvcOptions.ModelMetadataDetailsProviders coleção. O exemplo a seguir adiciona uma
System.Text.Json implementação baseada em ,
SystemTextJsonValidationMetadataProvider que formata nomes de propriedade como

camelCase por padrão:

C#

builder.Services.AddControllers();

builder.Services.Configure<MvcOptions>(options =>
{
options.ModelMetadataDetailsProviders.Add(
new SystemTextJsonValidationMetadataProvider());
});

SystemTextJsonValidationMetadataProvider também aceita uma implementação de

JsonNamingPolicy em seu construtor, que especifica uma política de nomenclatura


personalizada para formatar nomes de propriedade.

Para definir um nome personalizado para uma propriedade dentro de um modelo, use o
atributo [JsonPropertyName] na propriedade :

C#

public class SampleModel


{
[Range(1, 10)]
[JsonPropertyName("sampleValue")]
public int Value { get; set; }
}
A ValidationProblemDetails resposta retornada para o modelo anterior quando a Value
propriedade é inválida usa uma chave de erro de sampleValue , conforme mostrado no
exemplo a seguir:

C#

{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-00000000000000000000000000000000-000000000000000-00",
"errors": {
"sampleValue": [
"The field Value must be between 1 and 10."
]
}
}

Para formatar a ValidationProblemDetails resposta usando Newtonsoft.Json , use


NewtonsoftJsonValidationMetadataProvider :

C#

builder.Services.AddControllers()
.AddNewtonsoftJson();

builder.Services.Configure<MvcOptions>(options =>
{
options.ModelMetadataDetailsProviders.Add(
new NewtonsoftJsonValidationMetadataProvider());
});

Por padrão, NewtonsoftJsonValidationMetadataProvider formata nomes de propriedade


como camelCase. NewtonsoftJsonValidationMetadataProvider também aceita uma
implementação de NamingPolicy em seu construtor, que especifica uma política de
nomenclatura personalizada para formatar nomes de propriedade. Para definir um
nome personalizado para uma propriedade dentro de um modelo, use o
[JsonProperty] atributo .

Especificar um formato
Para restringir os formatos de resposta, aplique o [Produces] filtro. Como a maioria dos
Filtros, [Produces] pode ser aplicado na ação, controlador ou escopo global:

C#
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class TodoItemsController : ControllerBase

O filtro anterior [Produces] :

Força todas as ações dentro do controlador a retornar JSrespostas formatadas em


ON para POCOs (Objetos CLR Antigos Simples) ou ObjectResult seus tipos
derivados.
Retornar JSrespostas formatadas como ON mesmo que outros formatadores
estejam configurados e o cliente especifique um formato diferente.

Para obter mais informações, consulte Filtros.

Formatadores de maiúsculas e minúsculas


especiais
Alguns casos especiais são implementados com formatadores internos. Por padrão,
string os tipos de retorno são formatados como texto/sem formatação (texto/html , se
solicitado por meio do Accept cabeçalho). Esse comportamento pode ser excluído
removendo o StringOutputFormatter. Os formatadores são removidos em Program.cs .
Ações que têm um tipo de retorno de objeto de modelo retornam 204 No Content ao
retornar null . Esse comportamento pode ser excluído removendo o
HttpNoContentOutputFormatter. O código a seguir remove o StringOutputFormatter e
o HttpNoContentOutputFormatter .

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
// using Microsoft.AspNetCore.Mvc.Formatters;
options.OutputFormatters.RemoveType<StringOutputFormatter>();
options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
});

Sem o StringOutputFormatter , os formatos string de formatador ON internos


JSretornam tipos. Se o formatador ON interno JSfor removido e um formatador XML
estiver disponível, os formatos string de formatador XML retornarão tipos. Caso
contrário, string os tipos de retorno retornarão 406 Not Acceptable .
Sem o HttpNoContentOutputFormatter , os objetos nulos são formatados com o
formatador configurado. Por exemplo:

O JSformatador ON retorna uma resposta com um corpo de null .


O formatador XML retorna um elemento XML vazio com o conjunto de atributos
xsi:nil="true" .

Mapeamentos de URL de formato de resposta


Os clientes podem solicitar um formato específico como parte da URL, por exemplo:

Na cadeia de caracteres de consulta ou parte do caminho.


Usando uma extensão de arquivo específica de formato, como .xml ou .json.

O mapeamento do caminho da solicitação deve ser especificado na rota que está sendo
usada pela API. Por exemplo:

C#

[ApiController]
[Route("api/[controller]")]
[FormatFilter]
public class TodoItemsController : ControllerBase
{
private readonly TodoItemStore _todoItemStore;

public TodoItemsController(TodoItemStore todoItemStore)


=> _todoItemStore = todoItemStore;

[HttpGet("{id:long}.{format?}")]
public TodoItem? GetById(long id)
=> _todoItemStore.GetById(id);

A rota anterior permite que o formato solicitado seja especificado usando uma extensão
de arquivo opcional. O [FormatFilter] atributo verifica a existência do valor de formato
no RouteData e mapeia o formato de resposta para o formatador apropriado quando a
resposta é criada.

Rota Formatador

/api/todoitems/5 O formatador de saída padrão

/api/todoitems/5.json O JSformatador ON (se configurado)

/api/todoitems/5.xml O formatador XML (se configurado)


Desserialização polimórfica
Os recursos internos fornecem um intervalo limitado de serialização polimórfica, mas
nenhum suporte para desserialização. A desserialização requer um conversor
personalizado. Consulte Desserialização polimórfica para obter uma amostra completa
da desserialização polimórfica.

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Formatadores personalizados na API
Web ASP.NET Core
Artigo • 04/01/2023 • 10 minutos para o fim da leitura

ASP.NET Core MVC dá suporte à troca de dados em APIs Web usando formatores de
entrada e saída. Os formatadores de entrada são usados pela Associação de Modelos.
Os formatadores de saída são usados para formatar respostas.

A estrutura fornece formatores internos de entrada e saída para JSON e XML. Ele
fornece um formatador de saída interno para texto sem formatação, mas não fornece
um formatador de entrada para texto sem formatação.

Este artigo mostra como adicionar suporte para formatos adicionais criando
formatadores personalizados. Para obter um exemplo de um formatador de entrada de
texto simples personalizado, consulte TextPlainInputFormatter no GitHub.

Exibir ou baixar código de exemplo (como baixar)

Quando usar um formatador personalizado


Use um formatador personalizado para adicionar suporte a um tipo de conteúdo que
não seja tratado pelos formatadores internos.

Visão geral de como criar um formatador


personalizado
Para criar um formatador personalizado:

Para serializar dados enviados ao cliente, crie uma classe de formatador de saída.
Para desserializar os dados recebidos do cliente, crie uma classe de formatador de
entrada.
Adicionar instâncias de classes de formatação às coleções e OutputFormatters às
InputFormatters coleções em MvcOptions.

Criar um formatador personalizado


Para criar um formatador:
Derive a classe da classe base apropriada. O aplicativo de exemplo deriva de
TextOutputFormatter e TextInputFormatter.
Especifique os tipos de mídia e as codificações com suporte no construtor.
Substitua os métodos CanReadType e CanWriteType.
Substitua os métodos ReadRequestBodyAsync e WriteResponseBodyAsync.

O código a seguir mostra a VcardOutputFormatter classe do exemplo :

C#

public class VcardOutputFormatter : TextOutputFormatter


{
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));

SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}

protected override bool CanWriteType(Type? type)


=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);

public override async Task WriteResponseBodyAsync(


OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;

var logger =
serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();

if (context.Object is IEnumerable<Contact> contacts)


{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object!, logger);
}

await httpContext.Response.WriteAsync(buffer.ToString(),
selectedEncoding);
}

private static void FormatVcard(


StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");

logger.LogInformation("Writing {FirstName} {LastName}",


contact.FirstName, contact.LastName);
}
}

Derivar da classe base apropriada


Para tipos de mídia de texto (por exemplo, vCard), derivam da classe base ou
TextOutputFormatter da TextInputFormatter classe base:

C#

public class VcardOutputFormatter : TextOutputFormatter

Para tipos binários, derive da classe base ou OutputFormatter da InputFormatter classe


base.

Especificar tipos de mídia e codificações com suporte


No construtor, especifique os tipos de mídia e as codificações com suporte adicionando
ao e SupportedEncodings às SupportedMediaTypes coleções:

C#

public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));

SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}

Uma classe de formatador não pode usar injeção de construtor para suas dependências.
Por exemplo, ILogger<VcardOutputFormatter> não pode ser adicionado como um
parâmetro ao construtor. Para acessar serviços, use o objeto de contexto que é passado
para os métodos. Um exemplo de código neste artigo e o exemplo mostram como
fazer isso.
Substituir CanReadType e CanWriteType
Especifique o tipo para desserializar ou serializar substituindo os métodos ou
CanWriteType substituindo.CanReadType Por exemplo, para criar texto vCard de um
Contact tipo e vice-versa:

C#

protected override bool CanWriteType(Type? type)


=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);

O método CanWriteResult
Em alguns cenários, CanWriteResult deve ser substituído em vez de CanWriteType. Use
CanWriteResult se as condições a seguir forem verdadeiras:

O método de ação retorna uma classe de modelo.


Há classes derivadas que podem ser retornadas em runtime.
A classe derivada retornada pela ação deve ser conhecida em runtime.

Por exemplo, suponha que o método de ação:

A assinatura retorna um Person tipo.


Pode retornar um Student ou Instructor tipo que deriva de Person .

Para que o formatador manipule somente Student objetos, verifique o tipo do objeto
de Object contexto fornecido ao CanWriteResult método. Quando o método de ação
retornar IActionResult:

Não é necessário usar CanWriteResult .


O CanWriteType método recebe o tipo de runtime.

Substituir ReadRequestBodyAsync e
WriteResponseBodyAsync
A desserialização ou serialização é executada em ReadRequestBodyAsync ou
WriteResponseBodyAsync. O exemplo a seguir mostra como obter serviços do contêiner
de injeção de dependência. Os serviços não podem ser obtidos de parâmetros de
construtor:

C#
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;

var logger =
serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();

if (context.Object is IEnumerable<Contact> contacts)


{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object!, logger);
}

await httpContext.Response.WriteAsync(buffer.ToString(),
selectedEncoding);
}

private static void FormatVcard(


StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");

logger.LogInformation("Writing {FirstName} {LastName}",


contact.FirstName, contact.LastName);
}

Configurar o MVC para usar um formatador


personalizado
Para usar um formatador personalizado, adicione uma instância da classe de formatador
à coleção ou MvcOptions.OutputFormatters à MvcOptions.InputFormatters coleção:

C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});

Os formadores são avaliados na ordem em que são inseridos, em que o primeiro tem
precedência.

A classe completa VcardInputFormatter


O código a seguir mostra a VcardInputFormatter classe do exemplo :

C#

public class VcardInputFormatter : TextInputFormatter


{
public VcardInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));

SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}

protected override bool CanReadType(Type type)


=> type == typeof(Contact);

public override async Task<InputFormatterResult> ReadRequestBodyAsync(


InputFormatterContext context, Encoding effectiveEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;

var logger =
serviceProvider.GetRequiredService<ILogger<VcardInputFormatter>>();

using var reader = new StreamReader(httpContext.Request.Body,


effectiveEncoding);
string? nameLine = null;

try
{
await ReadLineAsync("BEGIN:VCARD", reader, context, logger);
await ReadLineAsync("VERSION:", reader, context, logger);

nameLine = await ReadLineAsync("N:", reader, context, logger);


var split = nameLine.Split(";".ToCharArray());
var contact = new Contact(FirstName: split[1], LastName:
split[0].Substring(2));

await ReadLineAsync("FN:", reader, context, logger);


await ReadLineAsync("END:VCARD", reader, context, logger);

logger.LogInformation("nameLine = {nameLine}", nameLine);

return await InputFormatterResult.SuccessAsync(contact);


}
catch
{
logger.LogError("Read failed: nameLine = {nameLine}", nameLine);
return await InputFormatterResult.FailureAsync();
}
}

private static async Task<string> ReadLineAsync(


string expectedText, StreamReader reader, InputFormatterContext
context,
ILogger logger)
{
var line = await reader.ReadLineAsync();

if (line is null || !line.StartsWith(expectedText))


{
var errorMessage = $"Looked for '{expectedText}' and got
'{line}'";

context.ModelState.TryAddModelError(context.ModelName,
errorMessage);
logger.LogError(errorMessage);

throw new Exception(errorMessage);


}

return line;
}
}

Testar o aplicativo
Execute o aplicativo de exemplo para este artigo , que implementa os formatores
básicos de entrada e saída do vCard. O aplicativo lê e grava vCards semelhante ao
seguinte formato:

BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD

Para ver a saída do vCard, execute o aplicativo e envie uma solicitação Get com o
cabeçalho text/vcard Accept para https://localhost:<port>/api/contacts .

Para adicionar um vCard à coleção de contatos na memória:

Envie uma solicitação Post com /api/contacts uma ferramenta como o Postman.
Defina o cabeçalho Content-Type como text/vcard .
Defina vCard o texto no corpo, formatado como o exemplo anterior.

Recursos adicionais
Formatar dados de resposta na API Web ASP.NET Core
Gerenciar referências de Protobuf com dotnet-grpc
Usar os analisadores da API Web
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

ASP.NET Core fornece um pacote de analisadores MVC destinado ao uso com projetos
de API Web. Os analisadores trabalham com controladores anotados com
ApiControllerAttribute, enquanto criam convenções de API Web.

O pacote de analisadores notifica você de qualquer ação do controlador que:

Retorna um código de status não declarado.


Retorna um resultado de sucesso não declarado.
Documenta um código de status que não é retornado.
Inclui uma verificação de validação de modelo explícita.

Referenciar o pacote do analisador


Os analisadores são incluídos no SDK do .NET Core. Para habilitar o analisador em seu
projeto, inclua a IncludeOpenAPIAnalyzers propriedade no arquivo de projeto:

XML

<PropertyGroup>
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
</PropertyGroup>

Analisadores para convenções de API Web


Documentos de OpenAPI contêm códigos de status e tipos de resposta que uma ação
pode retornar. No ASP.NET Core MVC, atributos como ProducesResponseTypeAttribute
e ProducesAttribute são usados para documentar uma ação. ASP.NET Core
documentação da API Web com o Swagger/OpenAPI entra em detalhes sobre como
documentar sua API Web.

Um dos analisadores no pacote inspeciona controladores anotados com


ApiControllerAttribute e identifica ações que não documentam totalmente as respostas.
Considere o seguinte exemplo:

C#

// GET api/contacts/{guid}
[HttpGet("{id}", Name = "GetById")]
[ProducesResponseType(typeof(Contact), StatusCodes.Status200OK)]
public IActionResult Get(string id)
{
var contact = _contacts.Get(id);

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.

Os analisadores exigem Microsoft.NET.Sdk.Web


Os analisadores não funcionam com projetos de biblioteca ou projetos referenciados
Sdk="Microsoft.NET.Sdk" .

Recursos adicionais
Usar convenções de API Web
Documentação da API Web ASP.NET Core com o Swagger/OpenAPI
Criar APIs Web com o ASP.NET Core
Usar convenções de API Web
Artigo • 28/11/2022 • 4 minutos para o fim da leitura

A documentação comum da API pode ser extraída e aplicada a várias ações,


controladores ou todos os controladores dentro de um assembly. As convenções de API
Web são um substituto para decorar ações individuais com [ProducesResponseType].

Uma convenção permite que você:

Defina os tipos de retorno mais comuns e códigos de status retornados de um tipo


de ação específico.
Identifica as ações que desviam do padrão definido.

As convenções padrão estão disponíveis em


Microsoft.AspNetCore.Mvc.DefaultApiConventions. As convenções são demonstradas
com o ValuesController.cs adicionado a um modelo de projeto de API :

C#

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace WebApp1.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}

// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}

// POST api/values
[HttpPost]
public void Post([FromBody] string value)
{
}

// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}

// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}

Ações que seguem os padrões no ValuesController.cs trabalho com as convenções


padrão. Se as convenções padrão não atenderem às suas necessidades, consulte Criar
convenções de API Web.

No runtime, Microsoft.AspNetCore.Mvc.ApiExplorer reconhece as convenções.


ApiExplorer é a abstração do MVC para comunicação com geradores de documento da

OpenAPI (também conhecida como Swagger). Os atributos da convenção aplicada


são associados a uma ação e estão incluídos na documentação da OpenAPI da ação. Os
Analisadores de API também reconhecem as convenções. Se a ação for não
convencional (por exemplo, ela retorna um código de status que não está documentado
pela convenção aplicada), um aviso incentivará você a fazer a documentação do código
de status.

Exibir ou baixar código de exemplo (como baixar)

Aplicar convenções de API Web


As convenções não fazem composição. Cada ação pode ser associada a exatamente
uma convenção. As convenções mais específicas têm precedência sobre as menos
específicas. A seleção é não determinística quando duas ou mais convenções da mesma
prioridade se aplicam a uma ação. As seguintes opções existem para aplicar uma
convenção a uma ação, da mais específica à menos específica:

1. Microsoft.AspNetCore.Mvc.ApiConventionMethodAttribute — aplica-se a ações


individuais e especifica o tipo de convenção e o método de convenção que se
aplica.

No exemplo a seguir, o método de convenção


Microsoft.AspNetCore.Mvc.DefaultApiConventions.Put do tipo de convenção
padrão é aplicado à ação Update :
C#

// PUT api/contactsconvention/{guid}
[HttpPut("{id}")]
[ApiConventionMethod(typeof(DefaultApiConventions),
nameof(DefaultApiConventions.Put))]
public IActionResult Update(string id, Contact contact)
{
var contactToUpdate = _contacts.Get(id);

if (contactToUpdate == null)
{
return NotFound();
}

_contacts.Update(contact);

return NoContent();
}

o método de convenção Microsoft.AspNetCore.Mvc.DefaultApiConventions.Put


aplica os seguintes atributos à ação:

C#

[ProducesDefaultResponseType]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]

Para saber mais sobre [ProducesDefaultResponseType] , confira Resposta padrão .

2. Microsoft.AspNetCore.Mvc.ApiConventionTypeAttribute aplicado a um controlador


– aplica o tipo de convenção especificado a todas as ações no controlador. Um
método de convenção é marcado com dicas que determinam as ações às quais o
método de convenção se aplica. Para obter mais informações sobre dicas, consulte
Criar convenções da API Web).

No exemplo a seguir, o conjunto padrão de convenções é aplicado a todas as


ações no ContactsConventionController:

C#

[ApiController]
[ApiConventionType(typeof(DefaultApiConventions))]
[Route("api/[controller]")]
public class ContactsConventionController : ControllerBase
{
3. Microsoft.AspNetCore.Mvc.ApiConventionTypeAttribute aplicado a um assembly –
aplica o tipo de convenção especificado a todos os controladores no assembly
atual. Como recomendação, aplique atributos de nível de assembly no Startup.cs
arquivo.

No exemplo a seguir, o conjunto padrão de convenções é aplicado a todos os


controladores no assembly:

C#

[assembly: ApiConventionType(typeof(DefaultApiConventions))]
namespace ApiConventions
{
public class Startup
{

Criar convenções de API Web


Se as convenções padrão da API não atenderem às suas necessidades, crie suas próprias
convenções. Uma convenção é:

um tipo estático com métodos.


Com capacidade de definir tipos de resposta e requisitos de nomenclatura em
ações.

Tipos de resposta
Esses métodos são anotados com atributos [ProducesResponseType] ou
[ProducesDefaultResponseType] . Por exemplo:

C#

public static class MyAppConventions


{
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public static void Find(int id)
{
}
}

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:

C#

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[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
Vídeo: Criar metadados para NSwagClient
Vídeo: Série do Iniciante para: APIs Web
Usar os analisadores da API Web
Documentação da API Web ASP.NET Core com o Swagger/OpenAPI
Como lidar com erros em APIs Web do
ASP.NET Core
Artigo • 20/12/2022 • 20 minutos para o fim da leitura

Este artigo descreve como lidar com erros e personalizar o tratamento de erros com
ASP.NET Core APIs Web.

Página de exceção do desenvolvedor


A Página de Exceção do Desenvolvedor mostra rastreamentos de pilha detalhados para
erros de servidor. Ele usa DeveloperExceptionPageMiddleware para capturar exceções
síncronas e assíncronas do pipeline HTTP e gerar respostas de erro. Por exemplo,
considere a seguinte ação do controlador, que gera uma exceção:

C#

[HttpGet("Throw")]
public IActionResult Throw() =>
throw new Exception("Sample exception.");

Quando a Página de Exceção do Desenvolvedor detecta uma exceção sem tratamento,


ela gera uma resposta de texto sem formatação padrão semelhante ao exemplo a
seguir:

Console

HTTP/1.1 500 Internal Server Error


Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.


at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
at lambda_method1(Closure , Object , Object[] )
at
Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResul
tExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor
executor, Object controller, Object[] arguments)
at
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeAction
MethodAsync()
at
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State&
next, Scope& scope, Object& state, Boolean& isCompleted)
at
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextAc
tionFilterAsync()

...

Se o cliente solicitar uma resposta formatada em HTML, a Página de Exceção do


Desenvolvedor gerará uma resposta semelhante ao seguinte exemplo:

Console

HTTP/1.1 500 Internal Server Error


Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Internal Server Error</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
font-size: .813em;
color: #222;
background-color: #fff;
}

h1 {
color: #44525e;
margin: 15px 0 15px 0;
}

...

Para solicitar uma resposta formatada em HTML, defina o cabeçalho da Accept


solicitação HTTP como text/html .

2 Aviso

Não habilite a Página de Exceção do Desenvolvedor , a menos que o aplicativo


esteja em execução no ambiente de desenvolvimento. Não compartilhe
informações detalhadas de exceção publicamente quando o aplicativo for
executado em produção. Para obter mais informações sobre como configurar
ambientes, consulte Usar vários ambientes no ASP.NET Core.
Manipulador de exceção
Em ambientes que não são de desenvolvimento, use o Middleware de Tratamento de
Exceção para produzir um conteúdo de erro:

1. Em Program.cs , chame UseExceptionHandler para adicionar o middleware de


tratamento de exceção:

C#

var app = builder.Build();

app.UseHttpsRedirection();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}

app.UseAuthorization();

app.MapControllers();

app.Run();

2. Configure uma ação do controlador para responder à /error rota:

C#

[Route("/error")]
public IActionResult HandleError() =>
Problem();

A ação anterior HandleError envia uma carga compatível com RFC 7807 para o
cliente.

2 Aviso

Não marque o método de ação do manipulador de erros com atributos de método


HTTP, como HttpGet . Verbos explícitos impedem que algumas solicitações
cheguem ao método de ação.

Para APIs Web que usam Swagger/OpenAPI, marque a ação do manipulador de


erros com o atributo [ApiExplorerSettings] e defina sua IgnoreApi propriedade
como true . Essa configuração de atributo exclui a ação do manipulador de erros
da especificação OpenAPI do aplicativo:
C#

[ApiExplorerSettings(IgnoreApi = true)]

Permita o acesso anônimo ao método se os usuários não autenticados deverem ver


o erro.

O Middleware de Tratamento de Exceções também pode ser usado no ambiente de


desenvolvimento para produzir um formato de carga consistente em todos os
ambientes:

1. No Program.cs , registre instâncias de Middleware específicas do ambiente de


Tratamento de Exceção:

C#

if (app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error-development");
}
else
{
app.UseExceptionHandler("/error");
}

No código anterior, o middleware é registrado com:

Uma rota de no ambiente de /error-development desenvolvimento.


Uma rota de /error em ambientes que não são de desenvolvimento.

2. Adicione ações do controlador para as rotas desenvolvimento e não


desenvolvimento:

C#

[Route("/error-development")]
public IActionResult HandleErrorDevelopment(
[FromServices] IHostEnvironment hostEnvironment)
{
if (!hostEnvironment.IsDevelopment())
{
return NotFound();
}

var exceptionHandlerFeature =
HttpContext.Features.Get<IExceptionHandlerFeature>()!;
return Problem(
detail: exceptionHandlerFeature.Error.StackTrace,
title: exceptionHandlerFeature.Error.Message);
}

[Route("/error")]
public IActionResult HandleError() =>
Problem();

Usar exceções para modificar a resposta


O conteúdo da resposta pode ser modificado de fora do controlador usando uma
exceção personalizada e um filtro de ação:

1. Crie um tipo de exceção conhecido chamado HttpResponseException :

C#

public class HttpResponseException : Exception


{
public HttpResponseException(int statusCode, object? value = null)
=>
(StatusCode, Value) = (statusCode, value);

public int StatusCode { get; }

public object? Value { get; }


}

2. Crie um filtro de ação chamado HttpResponseExceptionFilter :

C#

public class HttpResponseExceptionFilter : IActionFilter,


IOrderedFilter
{
public int Order => int.MaxValue - 10;

public void OnActionExecuting(ActionExecutingContext context) { }

public void OnActionExecuted(ActionExecutedContext context)


{
if (context.Exception is HttpResponseException
httpResponseException)
{
context.Result = new
ObjectResult(httpResponseException.Value)
{
StatusCode = httpResponseException.StatusCode
};

context.ExceptionHandled = true;
}
}
}

O filtro anterior especifica um Order do valor inteiro máximo menos 10. Isso Order
permite que outros filtros sejam executados no final do pipeline.

3. Em Program.cs , adicione o filtro de ação à coleção de filtros:

C#

builder.Services.AddControllers(options =>
{
options.Filters.Add<HttpResponseExceptionFilter>();
});

Resposta de erro de falha de validação


Para controladores de API Web, o MVC responde com um ValidationProblemDetails tipo
de resposta quando a validação do modelo falha. O MVC usa os resultados de
InvalidModelStateResponseFactory para construir a resposta de erro para uma falha de
validação. O exemplo a seguir substitui a fábrica padrão por uma implementação que
também dá suporte à formatação de respostas como XML, em Program.cs :

C#

builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
new BadRequestObjectResult(context.ModelState)
{
ContentTypes =
{
// using static System.Net.Mime.MediaTypeNames;
Application.Json,
Application.Xml
}
};
})
.AddXmlSerializerFormatters();
Resposta de erro do cliente
Um resultado de erro é definido como resultado com um código de status HTTP igual a
400 ou superior. Para controladores de API Web, o MVC transforma um resultado de
erro para produzir um ProblemDetails.

A criação automática de um ProblemDetails para códigos de status de erro é habilitada


por padrão, mas as respostas de erro podem ser configuradas de uma das seguintes
maneiras:

1. Usar o serviço de detalhes do problema


2. Implementar ProblemDetailsFactory
3. Usar ApiBehaviorOptions.ClientErrorMapping

Resposta de detalhes do problema padrão


O arquivo a seguir Program.cs foi gerado pelos modelos de aplicativo Web para
controladores de API:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Considere o seguinte controlador, que retorna BadRequest quando a entrada é inválida:

C#

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
// /api/values2/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
return BadRequest();
}

return Ok(Numerator / Denominator);


}

// /api/values2 /squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
return BadRequest();
}

return Ok(Math.Sqrt(radicand));
}
}

Uma resposta de detalhes do problema é gerada com o código anterior quando


qualquer uma das seguintes condições se aplica:

O /api/values2/divide ponto de extremidade é chamado com um denominador


zero.
O /api/values2/squareroot ponto de extremidade é chamado com um radicand
menor que zero.

O corpo da resposta de detalhes do problema padrão tem os seguintes type valores ,


title e status :

JSON

{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "Bad Request",
"status": 400,
"traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}

Serviço de detalhes do problema


ASP.NET Core dá suporte à criação de Detalhes do Problema para APIs HTTP usando
o IProblemDetailsService. Para obter mais informações, consulte o serviço Detalhes do
problema.
O código a seguir configura o aplicativo para gerar uma resposta de detalhes do
problema para todas as respostas de erro de cliente e servidor HTTP que ainda não têm
um conteúdo corporal:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

Considere o controlador de API da seção anterior, que retorna BadRequest quando a


entrada é inválida:

C#

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
// /api/values2/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
return BadRequest();
}

return Ok(Numerator / Denominator);


}

// /api/values2 /squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
return BadRequest();
}

return Ok(Math.Sqrt(radicand));
}
}

Uma resposta de detalhes do problema é gerada com o código anterior quando


qualquer uma das seguintes condições se aplica:

Uma entrada inválida é fornecida.


O URI não tem nenhum ponto de extremidade correspondente.
Ocorre uma exceção sem tratamento.

A criação automática de um ProblemDetails para códigos de status de erro fica


desabilitada quando a propriedade SuppressMapClientErrors é definida como true :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressMapClientErrors = true;
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Usando o código anterior, quando um controlador de API retorna BadRequest , um


status de resposta HTTP 400 é retornado sem corpo de resposta.
SuppressMapClientErrors impede que uma ProblemDetails resposta seja criada, mesmo

ao chamar WriteAsync um ponto de extremidade do Controlador de API. WriteAsync é


explicado posteriormente neste artigo.

A próxima seção mostra como personalizar o corpo da resposta de detalhes do


problema, usando CustomizeProblemDetails, para retornar uma resposta mais útil. Para
obter mais opções de personalização, consulte Personalizando detalhes do problema.
Personalizar detalhes do problema com CustomizeProblemDetails

O código a seguir usa ProblemDetailsOptions para definir CustomizeProblemDetails:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails = (context) =>
{

var mathErrorFeature = context.HttpContext.Features

.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
(string Detail, string Type) details =
mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError =>
("Divison by zero is not defined.",

"https://wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid
input.",

"https://wikipedia.org/wiki/Square_root")
};

context.ProblemDetails.Type = details.Type;
context.ProblemDetails.Title = "Bad Input";
context.ProblemDetails.Detail = details.Detail;
}
}
);

var app = builder.Build();

app.UseHttpsRedirection();

app.UseStatusCodePages();

app.UseAuthorization();

app.MapControllers();

app.Run();

O controlador de API atualizado:


C#

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
// /api/values/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
HttpContext.Features.Set(errorType);
return BadRequest();
}

return Ok(Numerator / Denominator);


}

// /api/values/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return BadRequest();
}
return Ok(Math.Sqrt(radicand));
}

O código a seguir contém o MathErrorFeature e MathErrorType , que são usados com o


exemplo anterior:

C#

// Custom Http Request Feature


class MathErrorFeature
{
public MathErrorType MathError { get; set; }
}
// Custom math errors
enum MathErrorType
{
DivisionByZeroError,
NegativeRadicandError
}

Uma resposta de detalhes do problema é gerada com o código anterior quando


qualquer uma das seguintes condições se aplica:

O /divide ponto de extremidade é chamado com um denominador zero.


O /squareroot ponto de extremidade é chamado com um radicand menor que
zero.
O URI não tem nenhum ponto de extremidade correspondente.

O corpo da resposta de detalhes do problema contém o seguinte quando qualquer


squareroot ponto de extremidade é chamado com um radicando menor que zero:

JSON

{
"type": "https://en.wikipedia.org/wiki/Square_root",
"title": "Bad Input",
"status": 400,
"detail": "Negative or complex numbers are not allowed."
}

Exibir ou baixar o código de exemplo

Implementa ProblemDetailsFactory
O MVC usa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory para
produzir todas as instâncias de ProblemDetails e ValidationProblemDetails. Esta fábrica é
usada para:

Respostas de erro do cliente


Respostas de erro de falha de validação
ControllerBase.Problem e ControllerBase.ValidationProblem

Para personalizar a resposta de detalhes do problema, registre uma implementação


personalizada de ProblemDetailsFactory em Program.cs :

C#

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory,
SampleProblemDetailsFactory>();

Use ApiBehaviorOptions.ClientErrorMapping .
Use a propriedade ClientErrorMapping para configurar o conteúdo da resposta
ProblemDetails . Por exemplo, o seguinte código em Program.cs atualiza a Link
propriedade para 404 respostas:

C#

builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});

Recursos adicionais
Como usar a validação ModelState na API Web ASP.NET Core
Exibir ou baixar o código de exemplo
Hellang.Middleware.ProblemDetails
Testar APIs Web com HttpRepl
Artigo • 28/11/2022 • 21 minutos para o fim da leitura

O HTTP REPL (Read-Eval-Print Loop) é:

Uma ferramenta de linha de comando leve e multiplataforma compatível com


todos os recursos compatíveis com o .NET Core.
Usado para fazer solicitações HTTP a fim de testar as APIs Web do ASP.NET Core (e
as APIs Web não pertencentes ao ASP.NET Core) e exibir os resultados.
Capaz de testar APIs Web hospedadas em qualquer ambiente, inclusive no
localhost e no Serviço de Aplicativo do Azure.

Os verbos HTTP a seguir são compatíveis:

DELETE
GET
HEAD
OPTIONS
PATCH
POST
PUT

Para acompanhar, exiba ou baixe a API Web de exemplo do ASP.NET Core (como
baixar).

Pré-requisitos
SDK do .NET Core 3.1

Instalação
Para instalar o HttpRepl, execute o seguinte comando:

CLI do .NET

dotnet tool install -g Microsoft.dotnet-httprepl

Uma ferramenta global do .NET Core é instalada pelo pacote do NuGet


Microsoft.dotnet-httprepl .
Uso
Após a instalação da ferramenta, execute o seguinte comando para iniciar o HttpRepl:

Console

httprepl

Para exibir os comandos HttpRepl disponíveis, execute um dos seguintes comandos:

Console

httprepl -h

Console

httprepl --help

É exibida a saída a seguir:

Console

Usage:
httprepl [<BASE_ADDRESS>] [options]

Arguments:
<BASE_ADDRESS> - The initial base address for the REPL.

Options:
-h|--help - Show help information.

Once the REPL starts, these commands are valid:

Setup Commands:
Use these commands to configure the tool for your API server

connect Configures the directory structure and base address of the


api server
set header Sets or clears a header for all requests. e.g. `set header
content-type application/json`

HTTP Commands:
Use these commands to execute requests against your application.

GET get - Issues a GET request


POST post - Issues a POST request
PUT put - Issues a PUT request
DELETE delete - Issues a DELETE request
PATCH patch - Issues a PATCH request
HEAD head - Issues a HEAD request
OPTIONS options - Issues a OPTIONS request

Navigation Commands:
The REPL allows you to navigate your URL space and focus on specific APIs
that you are working on.

ls Show all endpoints for the current path


cd Append the given directory to the currently selected path, or
move up a path when using `cd ..`

Shell Commands:
Use these commands to interact with the REPL shell.

clear Removes all text from the shell


echo [on/off] Turns request echoing on or off, show the request that was
made when using request commands
exit Exit the shell

REPL Customization Commands:


Use these commands to customize the REPL behavior.

pref [get/set] Allows viewing or changing preferences, e.g. 'pref set


editor.command.default 'C:\\Program Files\\Microsoft VS Code\\Code.exe'`
run Runs the script at the given path. A script is a set of
commands that can be typed with one command per line
ui Displays the Swagger UI page, if available, in the default
browser

Use `help <COMMAND>` for more detail on an individual command. e.g. `help
get`.
For detailed tool info, see https://aka.ms/http-repl-doc.

O HttpRepl oferece preenchimento de comando. Pressionar a tecla Tab itera na lista de


comandos que completam os caracteres ou o ponto de extremidade da API que você
digitou. As seções a seguir descrevem os comandos da CLI disponíveis.

Conectar-se à API Web


Conecte-se à uma API Web executando o seguinte comando:

Console

httprepl <ROOT URI>

<ROOT URI> é o URI de base para a API Web. Por exemplo:

Console
httprepl https://localhost:5001

Outra opção é executar o seguinte comando a qualquer momento enquanto HttpRepl


estiver em execução:

Console

connect <ROOT URI>

Por exemplo:

Console

(Disconnected)> connect https://localhost:5001

Apontar manualmente para a descrição do OpenAPI da


API Web
O comando connect acima tentará localizar automaticamente a descrição do OpenAPI.
Se isso não for possível por algum motivo, você poderá especificar o URI da descrição
do OpenAPI para a API Web usando a opção --openapi :

Console

connect <ROOT URI> --openapi <OPENAPI DESCRIPTION ADDRESS>

Por exemplo:

Console

(Disconnected)> connect https://localhost:5001 --openapi


/swagger/v1/swagger.json

Habilitar a saída detalhada para obter detalhes sobre


pesquisa, análise e validação de descrição do OpenAPI
Especificar a opção --verbose com o comando connect produzirá mais detalhes
quando a ferramenta pesquisar a descrição do OpenAPI, analisar e validá-la.

Console
connect <ROOT URI> --verbose

Por exemplo:

Console

(Disconnected)> connect https://localhost:5001 --verbose


Checking https://localhost:5001/swagger.json... 404 NotFound
Checking https://localhost:5001/swagger/v1/swagger.json... 404 NotFound
Checking https://localhost:5001/openapi.json... Found
Parsing... Successful (with warnings)
The field 'info' in 'document' object is REQUIRED [#/info]
The field 'paths' in 'document' object is REQUIRED [#/paths]

Navegar na API Web

Exibir os pontos de extremidade disponíveis


Para listar os diferentes pontos de extremidade (controladores) no caminho atual do
endereço da API Web, execute os comandos ls ou dir :

Console

https://localhost:5001/> ls

O seguinte formato de saída será exibido:

Console

. []
Fruits [get|post]
People [get|post]

https://localhost:5001/>

A saída anterior indica que há dois controladores disponíveis: Fruits e People . Ambos
os controladores são compatíveis com operações HTTP GET e POST sem parâmetro.

Navegar em um controlador específico revela mais detalhes. Por exemplo, a saída do


seguinte comando mostra que o controlador Fruits também é compatível com as
operações HTTP GET, PUT e DELETE. Cada uma dessas operações espera um parâmetro
id na rota:
Console

https://localhost:5001/fruits> ls
. [get|post]
.. []
{id} [get|put|delete]

https://localhost:5001/fruits>

Outra opção é executar o comando ui para abrir a página da interface do usuário do


Swagger da API Web em um navegador. Por exemplo:

Console

https://localhost:5001/> ui

Navegar até um ponto de extremidade


Para navegar até um ponto de extremidade diferente na API Web, execute o comando
cd :

Console

https://localhost:5001/> cd people

O caminho após o comando cd não diferencia maiúsculas de minúsculas. O seguinte


formato de saída será exibido:

Console

/people [get|post]

https://localhost:5001/people>

Personalizar o HttpRepl
As cores padrão do HttpRepl podem ser personalizadas. Além disso, um editor de texto
padrão pode ser definido. As preferências de HttpRepl são persistidas em toda a sessão
atual e serão respeitadas nas sessões futuras. Depois de modificadas, as preferências
são armazenadas no seguinte arquivo:

Windows
%USERPROFILE%\.httpreplprefs

O arquivo .httpreplprefs é carregado na inicialização e suas alterações não são


monitoradas no runtime. As modificações manuais no arquivo entram em vigor somente
após a reinicialização da ferramenta.

Exibir as configurações
Para exibir as configurações disponíveis, execute o comando pref get . Por exemplo:

Console

https://localhost:5001/> pref get

O comando anterior exibe os pares chave-valor disponíveis:

Console

colors.json=Green
colors.json.arrayBrace=BoldCyan
colors.json.comma=BoldYellow
colors.json.name=BoldMagenta
colors.json.nameSeparator=BoldWhite
colors.json.objectBrace=Cyan
colors.protocol=BoldGreen
colors.status=BoldYellow

Definir preferências de cores


No momento, as cores das respostas só são compatíveis com JSON. Para personalizar a
cor padrão da ferramenta HttpRepl, localize a chave correspondente à cor a ser alterada.
Para ver instruções sobre como localizar as chaves, confira a seção Exibir as
configurações. Por exemplo, altere o valor da chave colors.json de Green para White ,
desta forma:

Console

https://localhost:5001/people> pref set colors.json White

Somente as cores permitidas podem ser usadas. As solicitações HTTP subsequentes


exibem a saída com a nova cor.
Quando chaves de cor específicas não estão definidas, mais chaves genéricas são
consideradas. Para demonstrar esse comportamento de fallback, considere o seguinte
exemplo:

Se colors.json.name não tiver um valor, colors.json.string será usado.


Se colors.json.string não tiver um valor, colors.json.literal será usado.
Se colors.json.literal não tiver um valor, colors.json será usado.
Se colors.json não tiver um valor, a cor de texto padrão do shell de comando
( AllowedColors.None ) será usada.

Definir o tamanho do recuo


A personalização do tamanho do recuo da resposta só é compatível com JSON. O
tamanho padrão é dois espaços. Por exemplo:

JSON

[
{
"id": 1,
"name": "Apple"
},
{
"id": 2,
"name": "Orange"
},
{
"id": 3,
"name": "Strawberry"
}
]

Para alterar o tamanho padrão, defina a chave formatting.json.indentSize . Por


exemplo, para sempre usar quatro espaços:

Console

pref set formatting.json.indentSize 4

As respostas subsequentes respeitarão a configuração de quatro espaços:

JSON

[
{
"id": 1,
"name": "Apple"
},
{
"id": 2,
"name": "Orange"
},
{
"id": 3,
"name": "Strawberry"
}
]

Definir o editor de texto padrão


Por padrão, o HttpRepl não tem um editor de texto configurado para uso. Para testar os
métodos de API Web que exigem o corpo da solicitação HTTP, é preciso definir um
editor de texto padrão. A ferramenta HttpRepl inicia o editor de texto configurado
somente para escrever o corpo da solicitação. Execute o seguinte comando para definir
o editor de texto preferido como padrão:

Console

pref set editor.command.default "<EXECUTABLE>"

No comando anterior, <EXECUTABLE> é o caminho completo para o arquivo executável


do editor de texto. Por exemplo, execute o seguinte comando para definir o Visual
Studio Code como o editor de texto padrão:

Windows

Console

pref set editor.command.default "C:\Program Files\Microsoft VS


Code\Code.exe"

Para iniciar o editor de texto padrão com argumentos específicos da CLI, defina a chave
editor.command.default.arguments . Por exemplo, imagine que o Visual Studio Code é o
editor de texto padrão e que você quer que o HttpRepl sempre o abra em uma nova
sessão com as extensões desabilitadas. Execute o comando a seguir:

Console

pref set editor.command.default.arguments "--disable-extensions --new-


window"
 Dica

Se o editor padrão for o Visual Studio Code, você geralmente desejará passar o
argumento -w ou --wait para forçar o Visual Studio Code esperar que você feche
o arquivo antes de retornar.

Definir os caminhos de pesquisa de descrição do


OpenAPI
Por padrão, HttpRepl tem um conjunto de caminhos relativos que usa para localizar a
descrição do OpenAPI ao executar o comando connect sem a opção --openapi . Esses
caminhos relativos são combinados com os caminhos raiz e base especificados no
comando connect . Os caminhos relativos padrão são:

swagger.json
swagger/v1/swagger.json

/swagger.json

/swagger/v1/swagger.json
openapi.json

/openapi.json

Para usar um conjunto diferente de caminhos de pesquisa em seu ambiente, defina a


preferência swagger.searchPaths . O valor precisa ser uma lista delimitada por pipes de
caminhos relativos. Por exemplo:

Console

pref set swagger.searchPaths


"swagger/v2/swagger.json|swagger/v3/swagger.json"

Em vez de substituir completamente a lista padrão, a lista também pode ser modificada
adicionando ou removendo caminhos.

Para adicionar um ou mais caminhos de pesquisa à lista padrão, defina a preferência


swagger.addToSearchPaths . O valor precisa ser uma lista delimitada por pipes de

caminhos relativos. Por exemplo:

Console
pref set swagger.addToSearchPaths
"openapi/v2/openapi.json|openapi/v3/openapi.json"

Para remover um ou mais caminhos de pesquisa à lista padrão, defina a preferência


swagger.addToSearchPaths . O valor precisa ser uma lista delimitada por pipes de
caminhos relativos. Por exemplo:

Console

pref set swagger.removeFromSearchPaths "swagger.json|/swagger.json"

Testar solicitações HTTP GET

Sinopse
Console

get <PARAMETER> [-F|--no-formatting] [-h|--header] [--response:body] [--


response:headers] [-s|--streaming]

Argumentos
PARAMETER

O parâmetro de rota, se houver, esperado pelo método de ação do controlador


associado.

Opções
As opções a seguir estão disponíveis para o comando get :

-F|--no-formatting

Um sinalizador cuja presença suprime a formatação da resposta HTTP.

-h|--header

Define um cabeçalho para a solicitação HTTP. Os dois formatos de valor a seguir


são compatíveis:
{header}={value}
{header}:{value}

--response:body

Especifica um arquivo em que o corpo da resposta HTTP deve ser gravado. Por
exemplo, --response:body "C:\response.json" . Se ainda não existir, o arquivo será
criado.

--response:headers

Especifica um arquivo em que os cabeçalhos da resposta HTTP devem ser


gravados. Por exemplo, --response:headers "C:\response.txt" . Se ainda não
existir, o arquivo será criado.

-s|--streaming

Um sinalizador cuja presença habilita o streaming da resposta HTTP.

Exemplo
Para emitir uma solicitação HTTP GET:

1. Execute o comando get em um ponto de extremidade compatível:

Console

https://localhost:5001/people> get

O comando anterior exibirá o seguinte formato de saída:

Console

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 21 Jun 2019 03:38:45 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
{
"id": 1,
"name": "Scott Hunter"
},
{
"id": 2,
"name": "Scott Hanselman"
},
{
"id": 3,
"name": "Scott Guthrie"
}
]

https://localhost:5001/people>

2. Recupere um registro específico passando um parâmetro para o comando get :

Console

https://localhost:5001/people> get 2

O comando anterior exibirá o seguinte formato de saída:

Console

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 21 Jun 2019 06:17:57 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
{
"id": 2,
"name": "Scott Hanselman"
}
]

https://localhost:5001/people>

Testar solicitações HTTP POST

Sinopse
Console

post <PARAMETER> [-c|--content] [-f|--file] [-h|--header] [--no-body] [-F|--


no-formatting] [--response] [--response:body] [--response:headers] [-s|--
streaming]

Argumentos
PARAMETER

O parâmetro de rota, se houver, esperado pelo método de ação do controlador


associado.

Opções
-F|--no-formatting

Um sinalizador cuja presença suprime a formatação da resposta HTTP.

-h|--header

Define um cabeçalho para a solicitação HTTP. Os dois formatos de valor a seguir


são compatíveis:
{header}={value}
{header}:{value}

--response:body

Especifica um arquivo em que o corpo da resposta HTTP deve ser gravado. Por
exemplo, --response:body "C:\response.json" . Se ainda não existir, o arquivo será
criado.

--response:headers

Especifica um arquivo em que os cabeçalhos da resposta HTTP devem ser


gravados. Por exemplo, --response:headers "C:\response.txt" . Se ainda não
existir, o arquivo será criado.

-s|--streaming

Um sinalizador cuja presença habilita o streaming da resposta HTTP.

-c|--content

Fornece um corpo de solicitação HTTP embutido. Por exemplo, -c "


{"id":2,"name":"Cherry"}" .

-f|--file

Fornece um caminho para um arquivo que contém o corpo da solicitação HTTP.


Por exemplo, -f "C:\request.json" .

--no-body
Indica que nenhum corpo de solicitação HTTP é necessário.

Exemplo
Para emitir uma solicitação HTTP POST:

1. Execute o comando post em um ponto de extremidade compatível:

Console

https://localhost:5001/people> post -h Content-Type=application/json

No comando anterior, o cabeçalho da solicitação HTTP Content-Type está


configurado para indicar um tipo de mídia de corpo da solicitação do JSON. O
editor de texto padrão abrirá um arquivo .tmp com um modelo JSON que
representa o corpo da solicitação HTTP. Por exemplo:

JSON

{
"id": 0,
"name": ""
}

 Dica

Para definir o editor de texto padrão, confira a seção Definir o editor de texto
padrão.

2. Modifique o modelo JSON para satisfazer os requisitos de validação de modelo:

JSON

{
"id": 0,
"name": "Scott Addie"
}

3. Salve o arquivo .tmp e feche o editor de texto. A seguinte saída será exibida no
shell de comando:

Console
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Thu, 27 Jun 2019 21:24:18 GMT
Location: https://localhost:5001/people/4
Server: Kestrel
Transfer-Encoding: chunked

{
"id": 4,
"name": "Scott Addie"
}

https://localhost:5001/people>

Testar solicitações HTTP PUT

Sinopse
Console

put <PARAMETER> [-c|--content] [-f|--file] [-h|--header] [--no-body] [-F|--


no-formatting] [--response] [--response:body] [--response:headers] [-s|--
streaming]

Argumentos
PARAMETER

O parâmetro de rota, se houver, esperado pelo método de ação do controlador


associado.

Opções
-F|--no-formatting

Um sinalizador cuja presença suprime a formatação da resposta HTTP.

-h|--header

Define um cabeçalho para a solicitação HTTP. Os dois formatos de valor a seguir


são compatíveis:
{header}={value}
{header}:{value}

--response:body

Especifica um arquivo em que o corpo da resposta HTTP deve ser gravado. Por
exemplo, --response:body "C:\response.json" . Se ainda não existir, o arquivo será
criado.

--response:headers

Especifica um arquivo em que os cabeçalhos da resposta HTTP devem ser


gravados. Por exemplo, --response:headers "C:\response.txt" . Se ainda não
existir, o arquivo será criado.

-s|--streaming

Um sinalizador cuja presença habilita o streaming da resposta HTTP.

-c|--content

Fornece um corpo de solicitação HTTP embutido. Por exemplo, -c "


{"id":2,"name":"Cherry"}" .

-f|--file

Fornece um caminho para um arquivo que contém o corpo da solicitação HTTP.


Por exemplo, -f "C:\request.json" .

--no-body

Indica que nenhum corpo de solicitação HTTP é necessário.

Exemplo
Para emitir uma solicitação HTTP PUT:

1. Opcional: execute o comando get para exibir os dados antes de modificá-los:

Console

https://localhost:5001/fruits> get
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 22 Jun 2019 00:07:32 GMT
Server: Kestrel
Transfer-Encoding: chunked
[
{
"id": 1,
"data": "Apple"
},
{
"id": 2,
"data": "Orange"
},
{
"id": 3,
"data": "Strawberry"
}
]

2. Execute o comando put em um ponto de extremidade compatível:

Console

https://localhost:5001/fruits> put 2 -h Content-Type=application/json

No comando anterior, o cabeçalho da solicitação HTTP Content-Type está


configurado para indicar um tipo de mídia de corpo da solicitação do JSON. O
editor de texto padrão abrirá um arquivo .tmp com um modelo JSON que
representa o corpo da solicitação HTTP. Por exemplo:

JSON

{
"id": 0,
"name": ""
}

 Dica

Para definir o editor de texto padrão, confira a seção Definir o editor de texto
padrão.

3. Modifique o modelo JSON para satisfazer os requisitos de validação de modelo:

JSON

{
"id": 2,
"name": "Cherry"
}
4. Salve o arquivo .tmp e feche o editor de texto. A seguinte saída será exibida no
shell de comando:

Console

[main 2019-06-28T17:27:01.805Z] update#setState idle


HTTP/1.1 204 No Content
Date: Fri, 28 Jun 2019 17:28:21 GMT
Server: Kestrel

5. Opcional: emita um comando get para ver as modificações. Por exemplo, se você
digitou "Cherry" no editor de texto, um get retornará a seguinte saída:

Console

https://localhost:5001/fruits> get
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 22 Jun 2019 00:08:20 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
{
"id": 1,
"data": "Apple"
},
{
"id": 2,
"data": "Cherry"
},
{
"id": 3,
"data": "Strawberry"
}
]

https://localhost:5001/fruits>

Testar solicitações HTTP DELETE

Sinopse
Console
delete <PARAMETER> [-F|--no-formatting] [-h|--header] [--response] [--
response:body] [--response:headers] [-s|--streaming]

Argumentos
PARAMETER

O parâmetro de rota, se houver, esperado pelo método de ação do controlador


associado.

Opções
-F|--no-formatting

Um sinalizador cuja presença suprime a formatação da resposta HTTP.

-h|--header

Define um cabeçalho para a solicitação HTTP. Os dois formatos de valor a seguir


são compatíveis:
{header}={value}

{header}:{value}

--response:body

Especifica um arquivo em que o corpo da resposta HTTP deve ser gravado. Por
exemplo, --response:body "C:\response.json" . Se ainda não existir, o arquivo será
criado.

--response:headers

Especifica um arquivo em que os cabeçalhos da resposta HTTP devem ser


gravados. Por exemplo, --response:headers "C:\response.txt" . Se ainda não
existir, o arquivo será criado.

-s|--streaming

Um sinalizador cuja presença habilita o streaming da resposta HTTP.

Exemplo
Para emitir uma solicitação HTTP DELETE:
1. Opcional: execute o comando get para exibir os dados antes de modificá-los:

Console

https://localhost:5001/fruits> get
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 22 Jun 2019 00:07:32 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
{
"id": 1,
"data": "Apple"
},
{
"id": 2,
"data": "Orange"
},
{
"id": 3,
"data": "Strawberry"
}
]

2. Execute o comando delete em um ponto de extremidade compatível:

Console

https://localhost:5001/fruits> delete 2

O comando anterior exibirá o seguinte formato de saída:

Console

HTTP/1.1 204 No Content


Date: Fri, 28 Jun 2019 17:36:42 GMT
Server: Kestrel

3. Opcional: emita um comando get para ver as modificações. Neste exemplo, um


get retorna a seguinte saída:

Console

https://localhost:5001/fruits> get
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 22 Jun 2019 00:16:30 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
{
"id": 1,
"data": "Apple"
},
{
"id": 3,
"data": "Strawberry"
}
]

https://localhost:5001/fruits>

Testar solicitações HTTP PATCH

Sinopse
Console

patch <PARAMETER> [-c|--content] [-f|--file] [-h|--header] [--no-body] [-F|-


-no-formatting] [--response] [--response:body] [--response:headers] [-s|--
streaming]

Argumentos
PARAMETER

O parâmetro de rota, se houver, esperado pelo método de ação do controlador


associado.

Opções
-F|--no-formatting

Um sinalizador cuja presença suprime a formatação da resposta HTTP.

-h|--header

Define um cabeçalho para a solicitação HTTP. Os dois formatos de valor a seguir


são compatíveis:
{header}={value}

{header}:{value}

--response:body

Especifica um arquivo em que o corpo da resposta HTTP deve ser gravado. Por
exemplo, --response:body "C:\response.json" . Se ainda não existir, o arquivo será
criado.

--response:headers

Especifica um arquivo em que os cabeçalhos da resposta HTTP devem ser


gravados. Por exemplo, --response:headers "C:\response.txt" . Se ainda não
existir, o arquivo será criado.

-s|--streaming

Um sinalizador cuja presença habilita o streaming da resposta HTTP.

-c|--content

Fornece um corpo de solicitação HTTP embutido. Por exemplo, -c "


{"id":2,"name":"Cherry"}" .

-f|--file

Fornece um caminho para um arquivo que contém o corpo da solicitação HTTP.


Por exemplo, -f "C:\request.json" .

--no-body

Indica que nenhum corpo de solicitação HTTP é necessário.

Testar solicitações HTTP HEAD

Sinopse
Console

head <PARAMETER> [-F|--no-formatting] [-h|--header] [--response] [--


response:body] [--response:headers] [-s|--streaming]

Argumentos
PARAMETER

O parâmetro de rota, se houver, esperado pelo método de ação do controlador


associado.

Opções
-F|--no-formatting

Um sinalizador cuja presença suprime a formatação da resposta HTTP.

-h|--header

Define um cabeçalho para a solicitação HTTP. Os dois formatos de valor a seguir


são compatíveis:
{header}={value}
{header}:{value}

--response:body

Especifica um arquivo em que o corpo da resposta HTTP deve ser gravado. Por
exemplo, --response:body "C:\response.json" . Se ainda não existir, o arquivo será
criado.

--response:headers

Especifica um arquivo em que os cabeçalhos da resposta HTTP devem ser


gravados. Por exemplo, --response:headers "C:\response.txt" . Se ainda não
existir, o arquivo será criado.

-s|--streaming

Um sinalizador cuja presença habilita o streaming da resposta HTTP.

Testar solicitações HTTP OPTIONS

Sinopse
Console

options <PARAMETER> [-F|--no-formatting] [-h|--header] [--response] [--


response:body] [--response:headers] [-s|--streaming]
Argumentos
PARAMETER

O parâmetro de rota, se houver, esperado pelo método de ação do controlador


associado.

Opções
-F|--no-formatting

Um sinalizador cuja presença suprime a formatação da resposta HTTP.

-h|--header

Define um cabeçalho para a solicitação HTTP. Os dois formatos de valor a seguir


são compatíveis:
{header}={value}
{header}:{value}

--response:body

Especifica um arquivo em que o corpo da resposta HTTP deve ser gravado. Por
exemplo, --response:body "C:\response.json" . Se ainda não existir, o arquivo será
criado.

--response:headers

Especifica um arquivo em que os cabeçalhos da resposta HTTP devem ser


gravados. Por exemplo, --response:headers "C:\response.txt" . Se ainda não
existir, o arquivo será criado.

-s|--streaming

Um sinalizador cuja presença habilita o streaming da resposta HTTP.

Configurar cabeçalhos de solicitações HTTP


Para configurar um cabeçalho de solicitação HTTP, use uma das seguintes abordagens:

Configure embutido com a solicitação HTTP. Por exemplo:

Console
https://localhost:5001/people> post -h Content-Type=application/json

Com a abordagem anterior, cada cabeçalho de solicitação HTTP diferente exige


sua própria opção -h .

Configure antes de enviar a solicitação HTTP. Por exemplo:

Console

https://localhost:5001/people> set header Content-Type application/json

Ao configurar o cabeçalho antes de enviar a solicitação, ele permanecerá


configurado durante toda a sessão do shell de comando. Para limpar o cabeçalho,
forneça um valor vazio. Por exemplo:

Console

https://localhost:5001/people> set header Content-Type

Testar pontos de extremidade protegidos


O HttpRepl dá suporte ao teste de pontos de extremidade protegidos das seguintes
maneiras:

Por meio das credenciais padrão do usuário conectado.


Por meio do uso de cabeçalhos de solicitação HTTP.

Credenciais padrão
Considere uma API Web que você está testando que é hospedada no IIS e protegida
com a autenticação do Windows. Você quer que as credenciais do usuário que executa a
ferramenta fluam para os pontos de extremidade HTTP que estão sendo testados. Para
passar as credenciais padrão do usuário conectado:

1. Defina a preferência httpClient.useDefaultCredentials como true :

Console

pref set httpClient.useDefaultCredentials true

2. Saia e reinicie a ferramenta antes de enviar outra solicitação para a API Web.
Credenciais de proxy padrão
Considere um cenário no qual a API Web que você está testando está por trás de um
proxy protegido com autenticação do Windows. Você quer que as credenciais do
usuário que executa a ferramenta fluam para o proxy. Para passar as credenciais padrão
do usuário conectado:

1. Defina a preferência httpClient.proxy.useDefaultCredentials como true :

Console

pref set httpClient.proxy.useDefaultCredentials true

2. Saia e reinicie a ferramenta antes de enviar outra solicitação para a API Web.

Cabeçalhos de solicitação HTTP


Exemplos de esquemas de autenticação e autorização com suporte incluem:

basic authentication
Tokens de portador JWT
autenticação de código hash

Por exemplo, você pode enviar um token de portador para um ponto de extremidade
com o seguinte comando:

Console

set header Authorization "bearer <TOKEN VALUE>"

Para acessar um ponto de extremidade hospedado no Azure ou usar a API REST do


Azure, você precisa de um token de portador. Use as etapas a seguir para obter um
token de portador para sua assinatura do Azure por meio da CLI do Azure. HttpRepl
define o token de portador em um cabeçalho de solicitação HTTP. Uma lista de
Aplicativos Web do Serviço de Aplicativo do Azure é recuperada.

1. Entrar no Azure:

CLI do Azure

az login

2. Obtenha sua ID de assinatura com o seguinte comando:


CLI do Azure

az account show --query id

3. Copie a ID de assinatura e execute o seguinte comando:

CLI do Azure

az account set --subscription "<SUBSCRIPTION ID>"

4. Obtenha o token de portador com o seguinte comando:

CLI do Azure

az account get-access-token --query accessToken

5. Conecte-se à API REST do Azure por meio do HttpRepl:

Console

httprepl https://management.azure.com

6. Defina o cabeçalho da solicitação HTTP Authorization :

Console

https://management.azure.com/> set header Authorization "bearer <ACCESS


TOKEN>"

7. Navegue até a assinatura:

Console

https://management.azure.com/> cd subscriptions/<SUBSCRIPTION ID>

8. Obtenha uma lista de Aplicativos Web do Serviço de Aplicativo do Azure de sua


assinatura:

Console

https://management.azure.com/subscriptions/{SUBSCRIPTION ID}> get


providers/Microsoft.Web/sites?api-version=2016-08-01

A seguinte resposta é exibida:


Console

HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 35948
Content-Type: application/json; charset=utf-8
Date: Thu, 19 Sep 2019 23:04:03 GMT
Expires: -1
Pragma: no-cache
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
x-ms-correlation-request-id: <em>xxxxxxxx-xxxx-xxxx-xxxx-
xxxxxxxxxxxx</em>
x-ms-original-request-ids: <em>xxxxxxxx-xxxx-xxxx-xxxx-
xxxxxxxxxxxx;xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</em>
x-ms-ratelimit-remaining-subscription-reads: 11999
x-ms-request-id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
x-ms-routing-request-id: WESTUS:xxxxxxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-
xxxx-xxxxxxxxxx
{
"value": [
<AZURE RESOURCES LIST>
]
}

Alternar exibição da solicitação HTTP


Por padrão, a exibição da solicitação HTTP que está sendo enviada é suprimida. É
possível alterar a configuração correspondente durante a sessão do shell de comando.

Habilitar a exibição da solicitação


Exiba a solicitação HTTP que está sendo enviada executando o comando echo on . Por
exemplo:

Console

https://localhost:5001/people> echo on
Request echoing is on

As solicitações HTTP subsequentes na sessão atual exibem os cabeçalhos de solicitação.


Por exemplo:

Console

https://localhost:5001/people> post
[main 2019-06-28T18:50:11.930Z] update#setState idle
Request to https://localhost:5001...

POST /people HTTP/1.1


Content-Length: 41
Content-Type: application/json
User-Agent: HTTP-REPL

{
"id": 0,
"name": "Scott Addie"
}

Response from https://localhost:5001...

HTTP/1.1 201 Created


Content-Type: application/json; charset=utf-8
Date: Fri, 28 Jun 2019 18:50:21 GMT
Location: https://localhost:5001/people/4
Server: Kestrel
Transfer-Encoding: chunked

{
"id": 4,
"name": "Scott Addie"
}

https://localhost:5001/people>

Desabilitar a exibição da solicitação


Suprima a exibição da solicitação HTTP que está sendo enviada executando o comando
echo off . Por exemplo:

Console

https://localhost:5001/people> echo off


Request echoing is off

Executar um script
Se você executar com frequência o mesmo conjunto de comandos HttpRepl, considere
armazená-los em um arquivo de texto. Os comandos no arquivo assumem a mesma
forma que aqueles executados manualmente na linha de comando. Os comandos
podem ser executados em um modo em lote usando o comando run . Por exemplo:
1. Crie um arquivo de texto com um conjunto de comandos delimitados por nova
linha. Para ilustrar, considere um arquivo people-script.txt com os seguintes
comandos:

text

set base https://localhost:5001


ls
cd People
ls
get 1

2. Execute o comando run , passando o caminho do arquivo de texto. Por exemplo:

Console

https://localhost:5001/> run C:\http-repl-scripts\people-script.txt

O seguinte resultado é exibido:

Console

https://localhost:5001/> set base https://localhost:5001


Using OpenAPI description at
https://localhost:5001/swagger/v1/swagger.json

https://localhost:5001/> ls
. []
Fruits [get|post]
People [get|post]

https://localhost:5001/> cd People
/People [get|post]

https://localhost:5001/People> ls
. [get|post]
.. []
{id} [get|put|delete]

https://localhost:5001/People> get 1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 12 Jul 2019 19:20:10 GMT
Server: Kestrel
Transfer-Encoding: chunked

{
"id": 1,
"name": "Scott Hunter"
}
https://localhost:5001/People>

Limpar a saída
Para remover toda a saída gravada no shell de comando pela ferramenta HttpRepl,
execute os comandos clear ou cls . Para ilustrar, imagine que o shell de comando
contém a seguinte saída:

Console

httprepl https://localhost:5001
(Disconnected)> set base "https://localhost:5001"
Using OpenAPI description at https://localhost:5001/swagger/v1/swagger.json

https://localhost:5001/> ls
. []
Fruits [get|post]
People [get|post]

https://localhost:5001/>

Execute o comando a seguir para limpar a saída:

Console

https://localhost:5001/> clear

Após a execução o comando anterior, o shell de comando conterá somente a seguinte


saída:

Console

https://localhost:5001/>

Recursos adicionais
Solicitações da API REST
Repositório do GitHub do HttpRepl
Configurar o Visual Studio para iniciar o HttpRepl
Configurar o Visual Studio Code para iniciar o HttpRepl
Configurar o Visual Studio para Mac para iniciar o HttpRepl
Telemetria HttpRepl
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

O HttpRepl inclui um recurso de telemetria que coleta dados de uso. É importante que a
equipe httpRepl entenda como a ferramenta é usada para que ela possa ser aprimorada.

Como recusar
O recurso de telemetria HttpRepl está habilitado por padrão. Para recusar o recurso de
telemetria, defina a variável de ambiente DOTNET_HTTPREPL_TELEMETRY_OPTOUT como 1 ou
true .

Divulgação
O HttpRepl exibe texto semelhante ao seguinte quando você executa a ferramenta pela
primeira vez. O texto pode variar ligeiramente dependendo da versão da ferramenta
que você está executando. Essa experiência de “primeira execução” é como a Microsoft
notifica você sobre a coleta de dados.

Console

Telemetry
---------
The .NET tools collect usage data in order to help us improve your
experience. It is collected by Microsoft and shared with the community. You
can opt-out of telemetry by setting the DOTNET_HTTPREPL_TELEMETRY_OPTOUT
environment variable to '1' or 'true' using your favorite shell.

Para suprimir o texto da experiência "primeira execução", defina a variável de


DOTNET_HTTPREPL_SKIP_FIRST_TIME_EXPERIENCE ambiente como 1 ou true .

Pontos de dados
O recurso de telemetria não:

Colete dados pessoais, como nomes de usuário, endereços de email ou URLs.


Examine suas solicitações OU respostas HTTP.

Os dados são enviados com segurança para servidores microsoft e mantidos sob acesso
restrito.
A proteção de sua privacidade é importante para nós. Se você suspeitar que o recurso
de telemetria está coletando dados confidenciais ou se os dados estão sendo tratados
de forma insegura ou inadequada, execute uma das seguintes ações:

Arquive um problema no repositório dotnet/httprepl .


Envie um email para dotnet@microsoft.com investigação.

O recurso de telemetria coleta os dados a seguir.

Versões Dados
do SDK do
.NET

>=5,0 Carimbo de data/hora da invocação.

>=5,0 Endereço IP de três octetos usado para determinar a localização geográfica.

>=5,0 Sistema operacional e versão.

>=5,0 ID de runtime (RID) em que a ferramenta está em execução.

>=5,0 Se a ferramenta está em execução em um contêiner.

>=5,0 Endereço de Controle de Acesso de Mídia de Hash (MAC): um hash criptográfico


(SHA256) e uma ID exclusiva para um computador.

>=5,0 Versão do kernel.

>=5,0 Versão HttpRepl.

>=5,0 Se a ferramenta foi iniciada com help , run ou connect argumentos. Valores de
argumento reais não são coletados.

>=5,0 Comando invocado (por exemplo) get e se ele foi bem-sucedido.

>=5,0 Para o connect comando, se o root , base ou openapi argumentos foram


fornecidos. Valores de argumento reais não são coletados.

>=5,0 Para o pref comando, se um get ou set foi emitido e qual preferência foi
acessada. Se não for uma preferência bem conhecida, o nome será hash. O valor
não é coletado.

>=5,0 Para o set header comando, o nome do cabeçalho que está sendo definido. Se não
for um cabeçalho bem conhecido, o nome será hash. O valor não é coletado.

>=5,0 Para o connect comando, se um caso especial foi dotnet new webapi usado e se ele
foi ignorado por preferência.

>=5,0 Para todos os comandos HTTP (por exemplo, GET, POST, PUT), se cada uma das
opções foi especificada. Os valores das opções não são coletados.
Recursos adicionais
Telemetria do SDK do .NET Core
Dados de telemetria da CLI do .NET Core
Referência rápida de APIs mínimas
Artigo • 10/12/2022 • 60 minutos para o fim da leitura

Este documento:

Fornece uma referência rápida para APIs mínimas.


Destina-se a desenvolvedores experientes. Para obter uma introdução, consulte
Tutorial: Criar uma API Web mínima com ASP.NET Core

As APIs mínimas consistem em:

WebApplication e WebApplicationBuilder
Manipuladores de Rota

WebApplication
O código a seguir é gerado por um modelo de ASP.NET Core:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

O código anterior pode ser criado por meio dotnet new web da linha de comando ou da
seleção do modelo Web Vazio no Visual Studio.

O código a seguir cria um WebApplication ( app ) sem criar explicitamente um


WebApplicationBuilder:

C#

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create inicializa uma nova instância da WebApplication classe com


padrões pré-configurados.
Trabalhando com portas
Quando um aplicativo Web é criado com o Visual Studio ou dotnet new , um
Properties/launchSettings.json arquivo é criado que especifica as portas às quais o

aplicativo responde. Nos exemplos de configuração de porta a seguir, a execução do


aplicativo no Visual Studio retorna uma caixa de diálogo Unable to connect to web
server 'AppName' de erro . O Visual Studio retorna um erro porque espera a porta

especificada em Properties/launchSettings.json , mas o aplicativo está usando a porta


especificada por app.Run("http://localhost:3000") . Execute a porta a seguir alterando
exemplos da linha de comando.

As seções a seguir definem a porta à qual o aplicativo responde.

C#

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

No código anterior, o aplicativo responde à porta 3000 .

Várias portas
No código a seguir, o aplicativo responde à porta 3000 e 4000 ao .

C#

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Definir a porta da linha de comando


O comando a seguir faz com que o aplicativo responda à porta 7777 :

CLI do .NET
dotnet run --urls="https://localhost:7777"

Se o Kestrel ponto de extremidade também estiver configurado no appsettings.json


arquivo, a URL especificada do appsettings.json arquivo será usada. Para obter mais
informações, consulte Kestrel configuração de ponto de extremidade

Ler a porta do ambiente


O código a seguir lê a porta do ambiente:

C#

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

A maneira preferencial de definir a porta do ambiente é usar a ASPNETCORE_URLS variável


de ambiente, que é mostrada na seção a seguir.

Definir as portas por meio da variável de ambiente


ASPNETCORE_URLS

A ASPNETCORE_URLS variável de ambiente está disponível para definir a porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS dá suporte a várias URLs:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Escutar em todas as interfaces


Os exemplos a seguir demonstram a escuta em todas as interfaces
http://*:3000

C#

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

C#

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

C#

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Escutar em todas as interfaces usando


ASPNETCORE_URLS
Os exemplos anteriores podem usar ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Especificar HTTPS com certificado de desenvolvimento
C#

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Para obter mais informações sobre o certificado de desenvolvimento, consulte Confiar


no certificado de desenvolvimento HTTPS ASP.NET Core no Windows e no macOS.

Especificar HTTPS usando um certificado personalizado


As seções a seguir mostram como especificar o certificado personalizado usando o
appsettings.json arquivo e por meio da configuração.

Especificar o certificado personalizado com appsettings.json

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}

Especificar o certificado personalizado por meio da configuração

C#
var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key


builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Usar as APIs de certificado

C#

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(httpsOptions =>
{
var certPath = Path.Combine(builder.Environment.ContentRootPath,
"cert.pem");
var keyPath = Path.Combine(builder.Environment.ContentRootPath,
"key.pem");

httpsOptions.ServerCertificate =
X509Certificate2.CreateFromPemFile(certPath,
keyPath);
});
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Ler o ambiente
C#
var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");


app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

Para obter mais informações sobre como usar o ambiente, consulte Usar vários
ambientes no ASP.NET Core

Configuração
O código a seguir lê do sistema de configuração:

C#

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Hello";

app.MapGet("/", () => message);

app.Run();

Para obter mais informações, consulte Configuração no ASP.NET Core

Registro em log
O código a seguir grava uma mensagem na inicialização do aplicativo de logon:

C#

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Para obter mais informações, consulte Log no .NET Core e ASP.NET Core
Acessar o contêiner di (injeção de dependência)
O código a seguir mostra como obter serviços do contêiner de DI durante a inicialização
do aplicativo:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())


{
var sampleService =
scope.ServiceProvider.GetRequiredService<SampleService>();
sampleService.DoSomething();
}

app.Run();

Para obter mais informações, consulte Injeção de dependência no ASP.NET Core.

WebApplicationBuilder
Esta seção contém o código de exemplo usando WebApplicationBuilder.

Alterar a raiz do conteúdo, o nome do aplicativo e o


ambiente
O código a seguir define a raiz do conteúdo, o nome do aplicativo e o ambiente:

C#

var builder = WebApplication.CreateBuilder(new WebApplicationOptions


{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name:
{builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name:
{builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path:
{builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

O WebApplication.CreateBuilder inicializa uma nova instância da classe


WebApplicationBuilder com os padrões pré-configurados.

Para obter mais informações, consulte visão geral ASP.NET Core conceitos básicos

Alterar a raiz do conteúdo, o nome do aplicativo e o


ambiente por variáveis de ambiente ou linha de comando
A tabela a seguir mostra a variável de ambiente e o argumento de linha de comando
usados para alterar a raiz do conteúdo, o nome do aplicativo e o ambiente:

recurso Variável de ambiente Argumento de linha de comando

Nome do aplicativo ASPNETCORE_APPLICATIONNAME --Applicationname

Nome do ambiente ASPNETCORE_ENVIRONMENT --Ambiente

Raiz do conteúdo ASPNETCORE_CONTENTROOT --contentRoot

Adicionar provedores de configuração


O exemplo a seguir adiciona o provedor de configuração INI:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Para obter informações detalhadas, consulte Provedores de configuração de arquivos


em Configuração no ASP.NET Core.

Configuração de leitura
Por padrão, a WebApplicationBuilder configuração de leitura de várias fontes, incluindo:

appSettings.json e appSettings.{environment}.json
Variáveis de ambiente
A linha de comando

Para obter uma lista completa das fontes de configuração lida, consulte Configuração
padrão em Configuração no ASP.NET Core

C#

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Ler o ambiente
O código a seguir lê HelloKey a partir da configuração e exibe o valor no / ponto de
extremidade. Se o valor de configuração for nulo, "Hello" será atribuído a message :

C#

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Adicionar provedores de log


C#

var builder = WebApplication.CreateBuilder(args);


// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Adicionar serviços
C#

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.


builder.Services.AddMemoryCache();

// Add a custom scoped service.


builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Personalizar o IHostBuilder
Os métodos de extensão existentes no IHostBuilder podem ser acessados usando a
propriedade Host:

C#

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.


builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout =
TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Personalizar o IWebHostBuilder
Os métodos de extensão em IWebHostBuilder podem ser acessados usando a
propriedade WebApplicationBuilder.WebHost .
C#

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based


builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Alterar a raiz da Web


Por padrão, a raiz da Web é relativa à raiz de conteúdo na wwwroot pasta . Raiz da Web é
onde o middleware de arquivos estáticos procura arquivos estáticos. A raiz da Web
pode ser alterada com WebHostOptions , a linha de comando ou com o UseWebRoot
método :

C#

var builder = WebApplication.CreateBuilder(new WebApplicationOptions


{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Contêiner di (injeção de dependência personalizada)


O exemplo a seguir usa o Autofac :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't


// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();

Adicionar Middleware
Qualquer middleware ASP.NET Core existente pode ser configurado no WebApplication :

C#

var app = WebApplication.Create(args);

// Setup the file server to serve static files.


app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Para obter mais informações, consulte middleware ASP.NET Core

Página de exceção do desenvolvedor


WebApplication.CreateBuilder inicializa uma nova instância da WebApplicationBuilder
classe com padrões pré-configurados. A página de exceção do desenvolvedor está
habilitada nos padrões pré-configurados. Quando o código a seguir é executado no
ambiente de desenvolvimento, navegar para / renderiza uma página amigável que
mostra a exceção.

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an
exception.");
});

app.Run();

Middleware do ASP.NET Core


A tabela a seguir lista alguns dos middlewares usados com frequência com APIs
mínimas.

Middleware Descrição API

Autenticação Fornece suporte à autenticação. UseAuthentication

Autorização Fornece suporte à autorização. UseAuthorization

CORS Configura o Compartilhamento de UseCors


Recursos entre Origens.

Manipulador de Lida globalmente com exceções geradas UseExceptionHandler


exceção pelo pipeline de middleware.

Cabeçalhos Encaminha cabeçalhos como proxy para a UseForwardedHeaders


encaminhados solicitação atual.

Redirecionamento de Redireciona todas as solicitações HTTP UseHttpsRedirection


HTTPS para HTTPS.

Segurança de Middleware de aprimoramento de UseHsts


Transporte Estrita de segurança que adiciona um cabeçalho de
HTTP (HSTS) resposta especial.

Registro em log de Fornece suporte para registro em log de UseHttpLogging


solicitações solicitações e respostas HTTP.

Registro em log de Fornece suporte para registrar solicitações UseW3CLogging


solicitações do W3C HTTP e respostas no formato W3C .

Cache de resposta Fornece suporte para as respostas em UseResponseCaching


cache.

Compactação de Fornece suporte para a compactação de UseResponseCompression


resposta respostas.

Sessão Fornece suporte para gerenciar sessões de UseSession


usuário.

Arquivos estáticos Fornece suporte para servir arquivos UseStaticFiles,


estáticos e pesquisa no diretório. UseFileServer

WebSockets Habilita o protocolo WebSockets. UseWebSockets

Tratamento de solicitações
As seções a seguir abrangem roteamento, associação de parâmetros e respostas.
Roteamento
Um configurado WebApplication dá Map{Verb} suporte a e MapMethods:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "This is a GET");


app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");

app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },


() => "This is an options or head request ");

app.Run();

Manipuladores de rota
Manipuladores de rotas são métodos executados quando a rota corresponde. Os
manipuladores de rotas podem ser uma função de qualquer forma, incluindo síncrona
ou assíncrona. Os manipuladores de rotas podem ser uma expressão lambda, uma
função local, um método de instância ou um método estático.

Expressão lambda

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/inline", () => "This is an inline lambda");

var handler = () => "This is a lambda variable";

app.MapGet("/", handler);

app.Run();

Função local

C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

string LocalFunction() => "This is local function";

app.MapGet("/", LocalFunction);

app.Run();

Método de instância

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

var handler = new HelloHandler();

app.MapGet("/", handler.Hello);

app.Run();

class HelloHandler
{
public string Hello()
{
return "Hello Instance method";
}
}

Método estático

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", HelloHandler.Hello);

app.Run();

class HelloHandler
{
public static string Hello()
{
return "Hello static method";
}
}
Pontos de extremidade nomeados e geração de link
Os pontos de extremidade podem receber nomes para gerar URLs para o ponto de
extremidade. O uso de um ponto de extremidade nomeado evita a necessidade de
codificar caminhos em um aplicativo:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/hello", () => "Hello named route")


.WithName("hi");

app.MapGet("/", (LinkGenerator linker) =>


$"The link to the hello route is {linker.GetPathByName("hi", values:
null)}");

app.Run();

O código anterior é The link to the hello endpoint is /hello exibido do / ponto de
extremidade.

OBSERVAÇÃO: os nomes de ponto de extremidade diferenciam maiúsculas de


minúsculas.

Nomes de ponto de extremidade:

Deve ser globalmente exclusivo.


São usados como a ID da operação OpenAPI quando o suporte a OpenAPI está
habilitado. Para obter mais informações, consulte OpenAPI.

Parâmetros de rota
Os parâmetros de rota podem ser capturados como parte da definição do padrão de
rota:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/users/{userId}/books/{bookId}",
(int userId, int bookId) => $"The user id is {userId} and book id is
{bookId}");

app.Run();
O código anterior retorna The user id is 3 and book id is 7 do URI /users/3/books/7 .

O manipulador de rotas pode declarar os parâmetros a serem capturados. Quando uma


solicitação é feita uma rota com parâmetros declarados para captura, os parâmetros são
analisados e passados para o manipulador. Isso facilita a captura dos valores de uma
maneira fortemente tipada. No código anterior, userId e bookId são ambos int .

No código anterior, se um dos valores de rota não puder ser convertido em um int ,
uma exceção será gerada. A solicitação /users/hello/books/3 GET gera a seguinte
exceção:

BadHttpRequestException: Failed to bind parameter "int userId" from "hello".

Curinga e capturar todas as rotas


A seguinte captura todos os retornos Routing to hello de rota do ponto de
extremidade '/posts/hello':

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");

app.Run();

Restrições de rota
Restrições de rota restringem o comportamento correspondente de uma rota.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));


app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t =>
t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post
{slug}");

app.Run();

A tabela a seguir demonstra os modelos de rota anteriores e seu comportamento:


Modelo de rota URI de correspondência de exemplo

/todos/{id:int} /todos/1

/todos/{text} /todos/something

/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Para obter mais informações, consulte Referência de restrição de rota em Roteamento


em ASP.NET Core.

Grupos de rotas
O MapGroup método de extensão ajuda a organizar grupos de pontos de extremidade
com um prefixo comum. Ele reduz o código repetitivo e permite personalizar grupos
inteiros de pontos de extremidade com uma única chamada para métodos como
RequireAuthorization e WithMetadata que adicionam metadados de ponto de
extremidade.

Por exemplo, o código a seguir cria dois grupos semelhantes de pontos de extremidade:

C#

app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");

app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();

EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext
factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;

foreach (var argument in factoryContext.MethodInfo.GetParameters())


{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}

// Skip filter if the method doesn't have a TodoDb parameter.


if (dbContextIndex < 0)
{
return next;
}

return async invocationContext =>


{
var dbContext = invocationContext.GetArgument<TodoDb>
(dbContextIndex);
dbContext.IsPrivate = true;

try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise
reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}

C#

public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)


{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);

return group;
}

Nesse cenário, você pode usar um endereço relativo para o Location cabeçalho no 201
Created resultado:

C#

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb


database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();

return TypedResults.Created($"{todo.Id}", todo);


}
O primeiro grupo de pontos de extremidade corresponderá apenas às solicitações
prefixadas com /public/todos e estarão acessíveis sem nenhuma autenticação. O
segundo grupo de pontos de extremidade corresponderá apenas às solicitações
prefixadas com /private/todos e exigirá autenticação.

A QueryPrivateTodos fábrica de filtros de ponto de extremidade é uma função local que


modifica os parâmetros do manipulador de TodoDb rotas para permitir o acesso e o
armazenamento de dados de tarefas pendentes privados.

Os grupos de rotas também dão suporte a grupos aninhados e padrões de prefixo


complexos com parâmetros de rota e restrições. No exemplo a seguir, o manipulador de
rotas mapeado para o user grupo pode capturar os {org} parâmetros de rota e
{group} definidos nos prefixos do grupo externo.

O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou
filtros de ponto de extremidade a um grupo de pontos de extremidade sem alterar o
padrão de rota.

C#

var all = app.MapGroup("").WithOpenApi();


var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

Adicionar filtros ou metadados a um grupo se comporta da mesma maneira que


adicioná-los individualmente a cada ponto de extremidade antes de adicionar filtros ou
metadados extras que possam ter sido adicionados a um grupo interno ou ponto de
extremidade específico.

C#

var outer = app.MapGroup("/outer");


var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/inner group filter");
return next(context);
});

outer.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});

No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro


interno, mesmo que tenha sido adicionado em segundo lugar. Como os filtros foram
aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos
outros não importa. Os filtros de pedido são adicionados se forem aplicados ao mesmo
grupo ou ponto de extremidade específico.

Uma solicitação para /outer/inner/ registrará o seguinte:

CLI do .NET

/outer group filter


/inner group filter
MapGet filter

Associação de parâmetro
Associação de parâmetro é o processo de converter dados de solicitação em
parâmetros fortemente tipados que são expressos por manipuladores de rota. Uma
fonte de associação determina de onde os parâmetros são associados. As fontes de
associação podem ser explícitas ou inferidas com base no método HTTP e no tipo de
parâmetro.

Fontes de associação com suporte:

Valores de rota
Cadeia de consulta
Cabeçalho
Corpo (como JSON)
Serviços fornecidos pela injeção de dependência
Personalizado

Não há suporte nativo para associação de valores de formulário no .NET.

O manipulador de rotas a seguir GET usa algumas dessas fontes de associação de


parâmetros:

C#
var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,


int page,
[FromHeader(Name = "X-CUSTOM-HEADER")] string
customHeader,
Service service) => { });

class Service { }

A tabela a seguir mostra a relação entre os parâmetros usados no exemplo anterior e as


fontes de associação associadas.

Parâmetro Origem da associação

id valor de rota

page cadeia de caracteres de consulta

customHeader header

service Fornecido pela injeção de dependência

Os métodos GET HTTP , HEAD , OPTIONS e DELETE não se associam implicitamente do


corpo. Para associar do corpo (como JSON) para esses métodos HTTP, associe
explicitamente ou [FromBody] leia do HttpRequest.

O seguinte exemplo de manipulador de rotas POST usa uma origem de associação do


corpo (como JSON) para o person parâmetro :

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

Os parâmetros nos exemplos anteriores são todos associados de dados de solicitação


automaticamente. Para demonstrar a conveniência que a associação de parâmetro
fornece, os seguintes manipuladores de rotas mostram como ler dados de solicitação
diretamente da solicitação:

C#

app.MapGet("/{id}", (HttpRequest request) =>


{
var id = request.RouteValues["id"];
var page = request.Query["page"];
var customHeader = request.Headers["X-CUSTOM-HEADER"];

// ...
});

app.MapPost("/", async (HttpRequest request) =>


{
var person = await request.ReadFromJsonAsync<Person>();

// ...
});

Associação de parâmetro explícita


Os atributos podem ser usados para declarar explicitamente de onde os parâmetros são
associados.

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", ([FromRoute] int id,


[FromQuery(Name = "p")] int page,
[FromServices] Service service,
[FromHeader(Name = "Content-Type")] string contentType)
=> {});

class Service { }

record Person(string Name, int Age);

Parâmetro Origem da associação


Parâmetro Origem da associação

id valor de rota com o nome id

page cadeia de caracteres de consulta com o nome "p"

service Fornecido pela injeção de dependência

contentType cabeçalho com o nome "Content-Type"

7 Observação

Não há suporte nativo para associação de valores de formulário no .NET.

Associação de parâmetros com injeção de dependência


A associação de parâmetros para APIs mínimas associa parâmetros por meio de injeção
de dependência quando o tipo é configurado como um serviço. Não é necessário
aplicar explicitamente o [FromServices] atributo a um parâmetro. No código a seguir,
ambas as ações retornam a hora:

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/", ( IDateTime dateTime) => dateTime.Now);


app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

Parâmetros opcionais
Os parâmetros declarados em manipuladores de rota são tratados conforme necessário:

Se uma solicitação corresponder à rota, o manipulador de rotas só será executado


se todos os parâmetros necessários forem fornecidos na solicitação.
A falha ao fornecer todos os parâmetros necessários resulta em um erro.

C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page


{pageNumber}");

app.Run();

URI result

/products? 3 retornado
pageNumber=3

/products BadHttpRequestException : o parâmetro necessário "int pageNumber" não foi


fornecido da cadeia de caracteres de consulta.

/products/1 Erro HTTP 404, nenhuma rota correspondente

Para tornar pageNumber opcional, defina o tipo como opcional ou forneça um valor
padrão:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber


?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();

URI result

/products?pageNumber=3 3 retornado

/products 1 retornado

/products2 1 retornado

O valor padrão e anulável anteriores se aplica a todas as fontes:

C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/products", (Product? product) => { });

app.Run();

O código anterior chama o método com um produto nulo se nenhum corpo da


solicitação for enviado.

OBSERVAÇÃO: se dados inválidos forem fornecidos e o parâmetro for anulável, o


manipulador de rotas não será executado.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber


?? 1}");

app.Run();

URI result

/products? 3 Retornado
pageNumber=3

/products 1 Retornado

/products? BadHttpRequestException : falha ao associar o parâmetro "Nullable<int>


pageNumber=two pageNumber" de "dois".

/products/two Erro HTTP 404, nenhuma rota correspondente

Consulte a seção Falhas de Associação para obter mais informações.

Tipos especiais
Os seguintes tipos são associados sem atributos explícitos:

HttpContext: o contexto que contém todas as informações sobre a solicitação ou


resposta HTTP atual:

C#
app.MapGet("/", (HttpContext context) =>
context.Response.WriteAsync("Hello World"));

HttpRequest e HttpResponse: a solicitação HTTP e a resposta HTTP:

C#

app.MapGet("/", (HttpRequest request, HttpResponse response) =>


response.WriteAsync($"Hello World {request.Query["name"]}"));

CancellationToken: o token de cancelamento associado à solicitação HTTP atual:

C#

app.MapGet("/", async (CancellationToken cancellationToken) =>


await MakeLongRunningRequestAsync(cancellationToken));

ClaimsPrincipal: o usuário associado à solicitação, associado de HttpContext.User:

C#

app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);

Associar o corpo da solicitação como um Stream ou PipeReader


O corpo da solicitação pode ser associado como um Stream ou PipeReader para dar
suporte eficiente a cenários em que o usuário precisa processar dados e:

Armazene os dados no armazenamento de blobs ou enfileira os dados para um


provedor de filas.
Processe os dados armazenados com um processo de trabalho ou uma função de
nuvem.

Por exemplo, os dados podem ser enfileirados no Armazenamento de Filas do Azure ou


armazenados no Armazenamento de Blobs do Azure.

O código a seguir implementa uma fila em segundo plano:

C#

using System.Text.Json;
using System.Threading.Channels;

namespace BackgroundQueueService;
class BackgroundQueue : BackgroundService
{
private readonly Channel<ReadOnlyMemory<byte>> _queue;
private readonly ILogger<BackgroundQueue> _logger;

public BackgroundQueue(Channel<ReadOnlyMemory<byte>> queue,


ILogger<BackgroundQueue> logger)
{
_queue = queue;
_logger = logger;
}

protected override async Task ExecuteAsync(CancellationToken


stoppingToken)
{
await foreach (var dataStream in
_queue.Reader.ReadAllAsync(stoppingToken))
{
try
{
var person = JsonSerializer.Deserialize<Person>
(dataStream.Span)!;
_logger.LogInformation($"{person.Name} is {person.Age} " +
$"years and from {person.Country}");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
}
}

class Person
{
public string Name { get; set; } = String.Empty;
public int Age { get; set; }
public string Country { get; set; } = String.Empty;
}

O código a seguir associa o corpo da solicitação a um Stream :

C#

app.MapPost("/register", async (HttpRequest req, Stream body,


Channel<ReadOnlyMemory<byte>> queue) =>
{
if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
{
return Results.BadRequest();
}
// We're not above the message size and we have a content length, or
// we're a chunked request and we're going to read up to the
maxMessageSize + 1.
// We add one to the message size so that we can detect when a chunked
request body
// is bigger than our configured max.
var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

var buffer = new byte[readSize];

// Read at least that many bytes from the body.


var read = await body.ReadAtLeastAsync(buffer, readSize,
throwOnEndOfStream: false);

// We read more than the max, so this is a bad request.


if (read > maxMessageSize)
{
return Results.BadRequest();
}

// Attempt to send the buffer to the background queue.


if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
{
return Results.Accepted();
}

// We couldn't accept the message since we're overloaded.


return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

O código a seguir mostra o arquivo completo Program.cs :

C#

using System.Threading.Channels;
using BackgroundQueueService;

var builder = WebApplication.CreateBuilder(args);


// The max memory to use for the upload endpoint on this instance.
var maxMemory = 500 * 1024 * 1024;

// The max size of a single message, staying below the default LOH size of
85K.
var maxMessageSize = 80 * 1024;

// The max size of the queue based on those restrictions


var maxQueueSize = maxMemory / maxMessageSize;

// Create a channel to send data to the background queue.


builder.Services.AddSingleton<Channel<ReadOnlyMemory<byte>>>((_) =>
Channel.CreateBounded<ReadOnlyMemory<byte>>
(maxQueueSize));
// Create a background queue service.
builder.Services.AddHostedService<BackgroundQueue>();
var app = builder.Build();

// curl --request POST 'https://localhost:<port>/register' --header


'Content-Type: application/json' --data-raw '{ "Name":"Samson", "Age": 23,
"Country":"Nigeria" }'
// curl --request POST "https://localhost:<port>/register" --header
"Content-Type: application/json" --data-raw "{ \"Name\":\"Samson\", \"Age\":
23, \"Country\":\"Nigeria\" }"
app.MapPost("/register", async (HttpRequest req, Stream body,
Channel<ReadOnlyMemory<byte>> queue) =>
{
if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
{
return Results.BadRequest();
}

// We're not above the message size and we have a content length, or
// we're a chunked request and we're going to read up to the
maxMessageSize + 1.
// We add one to the message size so that we can detect when a chunked
request body
// is bigger than our configured max.
var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

var buffer = new byte[readSize];

// Read at least that many bytes from the body.


var read = await body.ReadAtLeastAsync(buffer, readSize,
throwOnEndOfStream: false);

// We read more than the max, so this is a bad request.


if (read > maxMessageSize)
{
return Results.BadRequest();
}

// Attempt to send the buffer to the background queue.


if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
{
return Results.Accepted();
}

// We couldn't accept the message since we're overloaded.


return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

app.Run();

Ao ler dados, o Stream é o mesmo objeto que HttpRequest.Body .


O corpo da solicitação não é armazenado em buffer por padrão. Depois que o
corpo é lido, ele não é rebobinável. O fluxo não pode ser lido várias vezes.
O Stream e PipeReader não são utilizáveis fora do manipulador de ação mínima,
pois os buffers subjacentes serão descartados ou reutilizados.

Uploads de arquivo usando IFormFile e IFormFileCollection

O código a seguir usa IFormFile e IFormFileCollection para carregar o arquivo:

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>


{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>


{
foreach (var file in myFiles)
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
}
});

app.Run();

Há suporte para solicitações de upload de arquivo autenticados usando um cabeçalho


de autorização , um certificado de cliente ou um cookie cabeçalho.

Não há suporte interno para antiforgeria. No entanto, ele pode ser implementado
usando o IAntiforgery serviço .

Associar matrizes e valores de cadeia de caracteres de


cabeçalhos e cadeias de caracteres de consulta
O código a seguir demonstra a associação de cadeias de caracteres de consulta a uma
matriz de tipos primitivos, matrizes de cadeia de caracteres e StringValues:
C#

// Bind query string values to a primitive type array.


// GET /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
$"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.


// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

Há suporte para associar cadeias de caracteres de consulta ou valores de cabeçalho a


uma matriz de tipos complexos quando o tipo é TryParse implementado. O código a
seguir é associado a uma matriz de cadeia de caracteres e retorna todos os itens com as
marcas especificadas:

C#

// GET /todoitems/tags?tags=home&tags=work
app.MapGet("/todoitems/tags", async (Tag[] tags, TodoDb db) =>
{
return await db.Todos
.Where(t => tags.Select(i => i.Name).Contains(t.Tag.Name))
.ToListAsync();
});

O código a seguir mostra o modelo e a implementação necessária TryParse :

C#

public class Todo


{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }

// This is an owned entity.


public Tag Tag { get; set; } = new();
}

[Owned]
public class Tag
{
public string? Name { get; set; } = "n/a";
public static bool TryParse(string? name, out Tag tag)
{
if (name is null)
{
tag = default!;
return false;
}

tag = new Tag { Name = name };


return true;
}
}

O código a seguir é associado a uma int matriz:

C#

// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
return await db.Todos
.Where(t => ids.Contains(t.Id))
.ToListAsync();
});

Para testar o código anterior, adicione o seguinte ponto de extremidade para preencher
o banco de dados com Todo itens:

C#

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();

return Results.Ok(todos);
});

Use uma ferramenta como o Postman para passar os seguintes dados para o ponto de
extremidade anterior:

C#

[
{
"id": 1,
"name": "Have Breakfast",
"isComplete": true,
"tag": {
"name": "home"
}
},
{
"id": 2,
"name": "Have Lunch",
"isComplete": true,
"tag": {
"name": "work"
}
},
{
"id": 3,
"name": "Have Supper",
"isComplete": true,
"tag": {
"name": "home"
}
},
{
"id": 4,
"name": "Have Snacks",
"isComplete": true,
"tag": {
"name": "N/A"
}
}
]

O código a seguir associa-se à chave X-Todo-Id de cabeçalho e retorna os Todo itens


com valores correspondentes Id :

C#

// GET /todoitems/header-ids
// The keys of the headers should all be X-Todo-Id with different values
app.MapGet("/todoitems/header-ids", async ([FromHeader(Name = "X-Todo-Id")]
int[] ids, TodoDb db) =>
{
return await db.Todos
.Where(t => ids.Contains(t.Id))
.ToListAsync();
});

Associação de parâmetros para listas de argumentos com


[AsParameters]
AsParametersAttribute habilita a associação de parâmetro simples a tipos e não a
associação de modelo complexa ou recursiva.

Considere o seguinte código:

C#

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>


await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
// Remaining code removed for brevity.

Considere o seguinte GET ponto de extremidade:

C#

app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());

O seguinte struct pode ser usado para substituir os parâmetros realçados anteriores:

C#

struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
O ponto de extremidade refatorado GET usa o anterior struct com o atributo
AsParameters :

C#

app.MapGet("/ap/todoitems/{id}",
async ([AsParameters] TodoItemRequest
request) =>
await request.Db.Todos.FindAsync(request.Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());

O código a seguir mostra pontos de extremidade adicionais no aplicativo:

C#

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>


{
var todoItem = new Todo
{
IsComplete = Dto.IsComplete,
Name = Dto.Name
};

Db.Todos.Add(todoItem);
await Db.SaveChangesAsync();

return Results.Created($"/todoitems/{todoItem.Id}", new


TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>


{
var todo = await Db.Todos.FindAsync(Id);

if (todo is null) return Results.NotFound();

todo.Name = Dto.Name;
todo.IsComplete = Dto.IsComplete;

await Db.SaveChangesAsync();

return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>


{
if (await Db.Todos.FindAsync(Id) is Todo todo)
{
Db.Todos.Remove(todo);
await Db.SaveChangesAsync();
return Results.Ok(new TodoItemDTO(todo));
}

return Results.NotFound();
});

As seguintes classes são usadas para refatorar as listas de parâmetros:

C#

class CreateTodoItemRequest
{
public TodoItemDTO Dto { get; set; } = default!;
public TodoDb Db { get; set; } = default!;
}

class EditTodoItemRequest
{
public int Id { get; set; }
public TodoItemDTO Dto { get; set; } = default!;
public TodoDb Db { get; set; } = default!;
}

O código a seguir mostra os pontos de extremidade refatorados usando AsParameters e


as classes e anteriores struct :

C#

app.MapPost("/ap/todoitems", async ([AsParameters] CreateTodoItemRequest


request) =>
{
var todoItem = new Todo
{
IsComplete = request.Dto.IsComplete,
Name = request.Dto.Name
};

request.Db.Todos.Add(todoItem);
await request.Db.SaveChangesAsync();

return Results.Created($"/todoitems/{todoItem.Id}", new


TodoItemDTO(todoItem));
});

app.MapPut("/ap/todoitems/{id}", async ([AsParameters] EditTodoItemRequest


request) =>
{
var todo = await request.Db.Todos.FindAsync(request.Id);

if (todo is null) return Results.NotFound();


todo.Name = request.Dto.Name;
todo.IsComplete = request.Dto.IsComplete;

await request.Db.SaveChangesAsync();

return Results.NoContent();
});

app.MapDelete("/ap/todoitems/{id}", async ([AsParameters] TodoItemRequest


request) =>
{
if (await request.Db.Todos.FindAsync(request.Id) is Todo todo)
{
request.Db.Todos.Remove(todo);
await request.Db.SaveChangesAsync();
return Results.Ok(new TodoItemDTO(todo));
}

return Results.NotFound();
});

Os seguintes record tipos podem ser usados para substituir os parâmetros anteriores:

C#

record TodoItemRequest(int Id, TodoDb Db);


record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

Usar um struct com AsParameters pode ser mais eficaz do que usar um record tipo.

O código de exemplo completo no repositório AspNetCore.Docs.Samples .

Associação personalizado
Há duas maneiras de personalizar a associação de parâmetros:

1. Para fontes de rota, consulta e associação de cabeçalho, associe tipos


personalizados adicionando um método estático TryParse para o tipo.
2. Controlar o processo de associação implementando um BindAsync método em um
tipo.

Tryparse

TryParse tem duas APIs:

C#
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out
result);

O código a seguir é Point: 12.3, 10.1 exibido com o URI /map?Point=12.3,10.1 :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point


{
public double X { get; set; }
public double Y { get; set; }

public static bool TryParse(string? value, IFormatProvider? provider,


out Point? point)
{
// Format is "(12.3,10.1)"
var trimmedValue = value?.TrimStart('(').TrimEnd(')');
var segments = trimmedValue?.Split(',',
StringSplitOptions.RemoveEmptyEntries |
StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& double.TryParse(segments[0], out var x)
&& double.TryParse(segments[1], out var y))
{
point = new Point { X = x, Y = y };
return true;
}

point = null;
return false;
}
}

BindAsync

BindAsync tem as seguintes APIs:

C#

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo


parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

O código a seguir é SortBy:xyz, SortDirection:Desc, CurrentPage:99 exibido com o


URI /products?SortBy=xyz&SortDir=Desc&Page=99 :

C#

using System.Reflection;

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy},
" +
$"SortDirection:{pageData.SortDirection}, CurrentPage:
{pageData.CurrentPage}");

app.Run();

public class PagingData


{
public string? SortBy { get; init; }
public SortDirection SortDirection { get; init; }
public int CurrentPage { get; init; } = 1;

public static ValueTask<PagingData?> BindAsync(HttpContext context,


ParameterInfo parameter)
{
const string sortByKey = "sortBy";
const string sortDirectionKey = "sortDir";
const string currentPageKey = "page";

Enum.TryParse<SortDirection>
(context.Request.Query[sortDirectionKey],
ignoreCase: true, out var
sortDirection);
int.TryParse(context.Request.Query[currentPageKey], out var page);
page = page == 0 ? 1 : page;

var result = new PagingData


{
SortBy = context.Request.Query[sortByKey],
SortDirection = sortDirection,
CurrentPage = page
};

return ValueTask.FromResult<PagingData?>(result);
}
}

public enum SortDirection


{
Default,
Asc,
Desc
}

Falhas de associação
Quando a associação falha, a estrutura registra uma mensagem de depuração e retorna
vários códigos de status para o cliente, dependendo do modo de falha.

Modo de falha Tipo de parâmetro Origem da Código


anulável associação de status

{ParameterType}.TryParse retorna sim route/query/header 400


false

{ParameterType}.BindAsync retorna sim custom 400


null

{ParameterType}.BindAsync Lança não importa custom 500

Falha ao desserializar o JScorpo ON não importa body 400

Tipo de conteúdo incorreto (não não importa body 415


application/json )

Precedência de associação
As regras para determinar uma origem de associação de um parâmetro:

1. Atributo explícito definido no parâmetro (atributos From*) na seguinte ordem:


a. Valores de rota: [FromRoute]
b. Cadeia de caracteres de consulta: [FromQuery]
c. Cabeçalho: [FromHeader]
d. Corpo: [FromBody]
e. Serviço: [FromServices]
f. Valores de parâmetro: [AsParameters]
2. Tipos especiais
a. HttpContext
b. HttpRequest (HttpContext.Request)
c. HttpResponse (HttpContext.Response)
d. ClaimsPrincipal (HttpContext.User)
e. CancellationToken (HttpContext.RequestAborted)
f. IFormFileCollection (HttpContext.Request.Form.Files)
g. IFormFile (HttpContext.Request.Form.Files[paramName])
h. Stream (HttpContext.Request.Body)
i. PipeReader (HttpContext.Request.BodyReader)
3. O tipo de parâmetro tem um método estático BindAsync válido.
4. O tipo de parâmetro é uma cadeia de caracteres ou tem um método estático
TryParse válido.
a. Se o nome do parâmetro existir no modelo de rota, por exemplo
app.Map("/todo/{id}", (int id) => {}); , , ele será associado da rota.

b. Associado da cadeia de caracteres de consulta.


5. Se o tipo de parâmetro for um serviço fornecido pela injeção de dependência, ele
usará esse serviço como a origem.
6. O parâmetro é do corpo.

Personalizar JSa associação ON


A fonte de associação do corpo usa para des System.Text.Json serialização. Não é
possível alterar esse padrão, mas a associação pode ser personalizada usando outras
técnicas descritas anteriormente. Para personalizar JSas opções do serializador ON, use
um código semelhante ao seguinte:

C#

using Microsoft.AspNetCore.Http.Json;

var builder = WebApplication.CreateBuilder(args);

// Configure JSON options.


builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/products", (Product product) => product);

app.Run();

class Product
{
// These are public fields, not properties.
public int Id;
public string? Name;
}

O código anterior:
Configura as opções on padrão JSde entrada e saída.
Retorna o seguinte JSON

JSON

{
"id": 1,
"name": "Joe Smith"
}

Ao postar

JSON

{
"Id": 1,
"Name": "Joe Smith"
}

Ler o corpo da solicitação


Leia o corpo da solicitação diretamente usando um HttpContext parâmetro ou
HttpRequest :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest


request) =>
{
var filePath = Path.Combine(config["StoredFilesPath"],
Path.GetRandomFileName());

await using var writeStream = File.Create(filePath);


await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

O código anterior:

Acessa o corpo da solicitação usando HttpRequest.BodyReader.


Copia o corpo da solicitação para um arquivo local.

Respostas
Os manipuladores de rota dão suporte aos seguintes tipos de valores retornados:

1. IResult baseado - Isso inclui Task<IResult> e ValueTask<IResult>


2. string - Isso inclui Task<string> e ValueTask<string>
3. T (Qualquer outro tipo) – isso inclui Task<T> e ValueTask<T>

Valor retornado Comportamento Tipo de conteúdo

IResult A estrutura chama IResult.ExecuteAsync Decidido pela IResult


implementação

string A estrutura grava a cadeia de caracteres text/plain


diretamente na resposta

T (Qualquer A estrutura on serializará JSa resposta application/json


outro tipo)

Para obter um guia mais aprofundado para valores retornados do manipulador de rotas,
consulte Criar respostas em aplicativos de API mínimos

Exemplo de valores retornados

valores retornados da cadeia de caracteres

C#

app.MapGet("/hello", () => "Hello World");

JSVALORES retornados ON

C#

app.MapGet("/hello", () => new { Message = "Hello World" });

Valores retornados de IResult

C#

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

O exemplo a seguir usa os tipos de resultados internos para personalizar a resposta:


C#

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);

JSEM

C#

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

Código de status personalizado

C#

app.MapGet("/405", () => Results.StatusCode(405));

Texto

C#

app.MapGet("/text", () => Results.Text("This is some text"));

STREAM

C#

var proxyClient = new HttpClient();


app.MapGet("/pokemon", async () =>
{
var stream = await
proxyClient.GetStreamAsync("http://consoto/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});

Consulte Criar respostas em aplicativos de API mínimos para obter mais exemplos.
Redirecionar

C#

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

Arquivo

C#

app.MapGet("/download", () => Results.File("myfile.text"));

Resultados internos
Auxiliares de resultados comuns existem na Microsoft.AspNetCore.Http.Results classe
estática.

Descrição Tipo de resposta Código de API


status

Defina o código de status aplicativo/json 200 Resultados.Ok


como 200, com uma resposta
ON opcional JS.

Escreva uma JSresposta ON application/json 200 Results.Json


com opções opcionais de (padrão),
serialização. configurável

Escreva uma resposta de texto/sem 200 Results.Text


texto. formatação
(padrão),
configurável

Escreva a resposta como application/octet- 200 Results.Bytes


bytes. stream (padrão),
configurável

Escreva um fluxo de bytes na application/octet- 200 Results.Stream


resposta. stream (padrão),
configurável

Transmita um arquivo para a application/octet- 200 Results.File


resposta para download com stream (padrão),
o cabeçalho de disposição de configurável
conteúdo.
Descrição Tipo de resposta Código de API
status

Defina o código de status aplicativo/json 201 Results.Created


como 201, com um cabeçalho
de localização e resposta ON
opcional JS.

Defina o código de status aplicativo/json 202 Results.Accepted


como 202, com um cabeçalho
de localização.

Defina o código de status N/D 204 Results.NoContent


como 204.

Defina o código de status N/D 400 Results.BadRequest


como 400, com uma resposta
ON opcional JS.

Escreva um objeto ON de N/D 400 Results.ValidationProblem


detalhes JSdo problema na
resposta com erros de
validação.

Defina o código de status N/D 401 Results.Unauthorized


como 401.

Defina o código de status N/D 404 Results.NotFound


como 404, com uma resposta
ON opcional JS.

Defina o código de status N/D 409 Results.Conflict


como 409, com uma resposta
ON opcional JS.

Defina o código de status N/D 422 Results.UnprocessableEntity


como 422, com uma resposta
ON opcional JS.

Escreva um objeto ON de N/D 500 Results.Problem


detalhes JSdo problema na (padrão),
resposta. configurável

Os mesmos auxiliares de resultado estão disponíveis na


Microsoft.AspNetCore.Http.TypedResults classe estática.

Personalizando resultados
Os aplicativos podem controlar as respostas implementando um tipo personalizado
IResult . O código a seguir é um exemplo de um tipo de resultado HTML:

C#

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions,
string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);

return new HtmlResult(html);


}
}

class HtmlResult : IResult


{
private readonly string _html;

public HtmlResult(string html)


{
_html = html;
}

public Task ExecuteAsync(HttpContext httpContext)


{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength =
Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}

Recomendamos adicionar um método de extensão para


Microsoft.AspNetCore.Http.IResultExtensions tornar esses resultados personalizados
mais detectáveis.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>


<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));

app.Run();

Resultados tipado
A IResult interface pode representar valores retornados de APIs mínimas que não
utilizam o suporte implícito para JSON serializando o objeto retornado para a resposta
HTTP. A classe Resultados estáticos é usada para criar objetos variados IResult que
representam diferentes tipos de respostas. Por exemplo, definir o código de status de
resposta ou redirecionar para outra URL.

Os tipos que implementam IResult são públicos, permitindo declarações de tipo ao


testar. Por exemplo:

C#

[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}

Você pode examinar os tipos de retorno dos métodos correspondentes na classe


TypedResults estática para localizar o tipo público IResult correto ao qual converter.

Consulte Criar respostas em aplicativos de API mínimos para obter mais exemplos.

Filtros
Consulte Filtros em aplicativos de API mínimos

Autorização
As rotas podem ser protegidas usando políticas de autorização. Eles podem ser
declarados por meio do [Authorize] atributo ou usando o RequireAuthorization método
:
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));

var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires


authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this
endpoint.");

app.Run();

O código anterior pode ser gravado com RequireAuthorization:

C#

app.MapGet("/auth", () => "This endpoint requires authorization")


.RequireAuthorization();

O exemplo a seguir usa autorização baseada em política:

C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));

var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () =>


"The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")


.RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");


app.MapGet("/Identity/Account/Login", () => "Sign in page at this
endpoint.");

app.Run();

Permitir que usuários não autenticados acessem um


ponto de extremidade
O [AllowAnonymous] permite que usuários não autenticados acessem pontos de
extremidade:

C#

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all


roles.");

app.MapGet("/login2", () => "This endpoint also for all roles.")


.AllowAnonymous();

CORS
As rotas podem ser habilitadas para CORS usando políticas cors. O CORS pode ser
declarado por meio do [EnableCors] atributo ou usando o RequireCors método . Os
seguintes exemplos habilitam o CORS:
C#

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});

var app = builder.Build();


app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();

C#

using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});

var app = builder.Build();


app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>


"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);

app.Run();
Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no
ASP.NET Core

Confira também
Suporte a OpenAPI em APIs mínimas
Filtros em aplicativos de API mínima
Artigo • 28/11/2022 • 6 minutos para o fim da leitura

Por Fiyaz Bin Hasan , Martin Costello e Rick Anderson

Filtros mínimos de API permitem que os desenvolvedores implementem a lógica de


negócios que dá suporte a:

Executando o código antes e depois do manipulador de ponto de extremidade.


Inspecionar e modificar parâmetros fornecidos durante uma invocação do
manipulador de ponto de extremidade.
Interceptando o comportamento de resposta de um manipulador de ponto de
extremidade.

Os filtros podem ser úteis nos seguintes cenários:

Validando os parâmetros de solicitação e o corpo que são enviados para um ponto


de extremidade.
Registrar em log informações sobre a solicitação e a resposta.
Validar se uma solicitação tem como destino uma versão de API com suporte.

Os filtros podem ser registrados fornecendo um Delegado que usa um


EndpointFilterInvocationContext e retorna um EndpointFilterDelegate . O
EndpointFilterInvocationContext fornece acesso ao HttpContext da solicitação e uma
Arguments lista que indica os argumentos passados para o manipulador na ordem em

que aparecem na declaração do manipulador.

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

string ColorName(string color) => $"Color specified: {color}!";

app.MapGet("/colorSelector/{color}", ColorName)
.AddEndpointFilter(async (invocationContext, next) =>
{
var color = invocationContext.GetArgument<string>(0);

if (color == "Red")
{
return Results.Problem("Red not allowed!");
}
return await next(invocationContext);
});
app.Run();

O código anterior:

Chama o AddEndpointFilter método de extensão para adicionar um filtro ao


/colorSelector/{color} ponto de extremidade.

Retorna a cor especificada, exceto pelo valor "Red" .


Retorna Results.Problem quando o /colorSelector/Red é solicitado.
next Usa como EndpointFilterDelegate e invocationContext como o

EndpointFilterInvocationContext para invocar o próximo filtro no pipeline ou o


delegado de solicitação se o último filtro tiver sido invocado.

O filtro é executado antes do manipulador de ponto de extremidade. Quando várias


AddEndpointFilter invocações são feitas em um manipulador:

O código de filtro chamado antes que o EndpointFilterDelegate ( next ) seja


chamado é executado na ordem fifo (primeiro a entrar, primeiro a sair).
O código de filtro chamado depois que o EndpointFilterDelegate ( next ) é
chamado é executado por ordem de ordem FILO (First In, Last Out).

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
app.Logger.LogInformation(" Endpoint");
return "Test of multiple filters";
})
.AddEndpointFilter(async (efiContext, next) =>
{
app.Logger.LogInformation("Before first filter");
var result = await next(efiContext);
app.Logger.LogInformation("After first filter");
return result;
})
.AddEndpointFilter(async (efiContext, next) =>
{
app.Logger.LogInformation(" Before 2nd filter");
var result = await next(efiContext);
app.Logger.LogInformation(" After 2nd filter");
return result;
})
.AddEndpointFilter(async (efiContext, next) =>
{
app.Logger.LogInformation(" Before 3rd filter");
var result = await next(efiContext);
app.Logger.LogInformation(" After 3rd filter");
return result;
});

app.Run();

No código anterior, os filtros e o ponto de extremidade registram a seguinte saída:

CLI do .NET

Before first filter


Before 2nd filter
Before 3rd filter
Endpoint
After 3rd filter
After 2nd filter
After first filter

O código a seguir usa filtros que implementam a IEndpointFilter interface :

C#

using Filters.EndpointFilters;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
app.Logger.LogInformation("Endpoint");
return "Test of multiple filters";
})
.AddEndpointFilter<AEndpointFilter>()
.AddEndpointFilter<BEndpointFilter>()
.AddEndpointFilter<CEndpointFilter>();

app.Run();

No código anterior, os logs de filtros e manipuladores mostram a ordem em que são


executados:

CLI do .NET

AEndpointFilter Before next


BEndpointFilter Before next
CEndpointFilter Before next
Endpoint
CEndpointFilter After next
BEndpointFilter After next
AEndpointFilter After next

Os filtros que implementam a IEndpointFilter interface são mostrados no exemplo a


seguir:

C#

namespace Filters.EndpointFilters;

public abstract class ABCEndpointFilters : IEndpointFilter


{
protected readonly ILogger Logger;
private readonly string _methodName;

protected ABCEndpointFilters(ILoggerFactory loggerFactory)


{
Logger = loggerFactory.CreateLogger<ABCEndpointFilters>();
_methodName = GetType().Name;
}

public virtual async ValueTask<object?>


InvokeAsync(EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
Logger.LogInformation("{MethodName} Before next", _methodName);
var result = await next(context);
Logger.LogInformation("{MethodName} After next", _methodName);
return result;
}
}

class AEndpointFilter : ABCEndpointFilters


{
public AEndpointFilter(ILoggerFactory loggerFactory) :
base(loggerFactory) { }
}

class BEndpointFilter : ABCEndpointFilters


{
public BEndpointFilter(ILoggerFactory loggerFactory) :
base(loggerFactory) { }
}

class CEndpointFilter : ABCEndpointFilters


{
public CEndpointFilter(ILoggerFactory loggerFactory) :
base(loggerFactory) { }
}
Validar um objeto com um filtro
Considere um filtro que valide um Todo objeto :

C#

app.MapPut("/todoitems/{id}", async (Todo inputTodo, int id, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
}).AddEndpointFilter(async (efiContext, next) =>
{
var tdparam = efiContext.GetArgument<Todo>(0);

var validationError = Utilities.IsValid(tdparam);

if (!string.IsNullOrEmpty(validationError))
{
return Results.Problem(validationError);
}
return await next(efiContext);
});

No código anterior:

O EndpointFilterInvocationContext objeto fornece acesso aos parâmetros


associados a uma solicitação específica emitida para o ponto de extremidade por
meio do GetArguments método .
O filtro é registrado usando um delegate que usa um
EndpointFilterInvocationContext e retorna um EndpointFilterDelegate .

Além de serem passados como delegados, os filtros podem ser registrados


implementando a IEndpointFilter interface . O código a seguir mostra o filtro anterior
encapsulado em uma classe que implementa IEndpointFilter :

C#

public class TodoIsValidFilter : IEndpointFilter


{
private ILogger _logger;
public TodoIsValidFilter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<TodoIsValidFilter>();
}

public async ValueTask<object?>


InvokeAsync(EndpointFilterInvocationContext efiContext,
EndpointFilterDelegate next)
{
var todo = efiContext.GetArgument<Todo>(0);

var validationError = Utilities.IsValid(todo!);

if (!string.IsNullOrEmpty(validationError))
{
_logger.LogWarning(validationError);
return Results.Problem(validationError);
}
return await next(efiContext);
}
}

Os filtros que implementam a IEndpointFilter interface podem resolver dependências


da DI (Injeção de Dependência), conforme mostrado no código anterior. Embora os
filtros possam resolver dependências da DI, os próprios filtros não podem ser resolvidos
da DI.

O ToDoIsValidFilter é aplicado aos seguintes pontos de extremidade:

C#

app.MapPut("/todoitems2/{id}", async (Todo inputTodo, int id, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
}).AddEndpointFilter<TodoIsValidFilter>();

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


}).AddEndpointFilter<TodoIsValidFilter>();
O filtro a seguir valida o Todo objeto e modifica a Name propriedade :

C#

public class TodoIsValidUcFilter : IEndpointFilter


{
public async ValueTask<object?>
InvokeAsync(EndpointFilterInvocationContext efiContext,
EndpointFilterDelegate next)
{
var todo = efiContext.GetArgument<Todo>(0);
todo.Name = todo.Name!.ToUpper();

var validationError = Utilities.IsValid(todo!);

if (!string.IsNullOrEmpty(validationError))
{
return Results.Problem(validationError);
}
return await next(efiContext);
}
}

Registrar um filtro usando uma fábrica de


filtros de ponto de extremidade
Em alguns cenários, pode ser necessário armazenar em cache algumas das informações
fornecidas no MethodInfo em um filtro. Por exemplo, vamos supor que queríamos
verificar se o manipulador ao qual um filtro de ponto de extremidade está anexado tem
um primeiro parâmetro que é avaliado como um Todo tipo.

C#

app.MapPut("/todoitems/{id}", async (Todo inputTodo, int id, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
}).AddEndpointFilterFactory((filterFactoryContext, next) =>
{
var parameters = filterFactoryContext.MethodInfo.GetParameters();
if (parameters.Length >= 1 && parameters[0].ParameterType ==
typeof(Todo))
{
return async invocationContext =>
{
var todoParam = invocationContext.GetArgument<Todo>(0);

var validationError = Utilities.IsValid(todoParam);

if (!string.IsNullOrEmpty(validationError))
{
return Results.Problem(validationError);
}
return await next(invocationContext);
};
}
return invocationContext => next(invocationContext);
});

No código anterior:

O EndpointFilterFactoryContext objeto fornece acesso ao MethodInfo associado


ao manipulador do ponto de extremidade.
A assinatura do manipulador é examinada inspecionando MethodInfo.Parameters
a assinatura de tipo esperada. Se a assinatura esperada for encontrada, o filtro de
validação será registrado no ponto de extremidade. Esse padrão de fábrica é útil
para registrar um filtro que depende da assinatura do manipulador de ponto de
extremidade de destino.
Se uma assinatura correspondente não for encontrada, um filtro de passagem será
registrado.

Registrar um filtro nas ações do controlador


Em alguns cenários, pode ser necessário aplicar a mesma lógica de filtro para pontos de
extremidade baseados em manipulador de rotas e ações do controlador. Para esse
cenário, é possível invocar para ControllerActionEndpointConventionBuilder dar
AddEndpointFilter suporte à execução da mesma lógica de filtro em ações e pontos de

extremidade.

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapController()
.AddEndpointFilter(async (efiContext, next) =>
{
efiContext.HttpContext.Items["endpointFilterCalled"] = true;
var result = await next(efiContext);
return result;
});

app.Run();

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
ValidationFilterRouteHandlerBuilderExtensions Métodos de extensão de
validação.
Tutorial: Criar uma API Web mínima com ASP.NET Core
Autenticação e autorização em APIs mínimas
Visão geral do ASP.NET Core SignalR
Artigo • 28/11/2022 • 2 minutos para o fim da leitura

O que é o SignalR?
SignalR ASP.NET Core é uma biblioteca de software livre que simplifica a adição de
funcionalidade da Web em tempo real aos aplicativos. A funcionalidade da Web em
tempo real permite que o código do lado do servidor envie conteúdo por push para os
clientes instantaneamente.

Bons candidatos para SignalR:

Aplicativos que exigem atualizações frequentes do servidor. Por exemplo, jogos,


redes sociais, votação, leilão, mapas e aplicativos de GPS.
Painéis e aplicativos de monitoramento. Por exemplo, painéis de empresa,
atualizações de vendas instantâneas ou alertas de viagem.
Aplicativos de colaboração. Aplicativos de quadro de comunicação e software de
reunião de equipe são exemplos de aplicativos de colaboração.
Aplicativos que exigem notificações. Redes sociais, email, chat, jogos, alertas de
viagem e muitos outros aplicativos usam notificações.

SignalR fornece uma API para criar RPC (chamadas de procedimento remoto de
servidor para cliente). Os RPCs invocam funções em clientes do código .NET Core do
lado do servidor. Há várias plataformas com suporte, cada uma com seu respectivo SDK
de cliente. Por isso, a linguagem de programação que está sendo invocada pela
chamada RPC varia.

Aqui estão alguns recursos de SignalR ASP.NET Core:

Manipula o gerenciamento de conexões automaticamente.


Envia mensagens para todos os clientes conectados simultaneamente. Por
exemplo, uma sala de chat.
Envia mensagens para clientes ou grupos de clientes específicos.
Dimensiona para lidar com o aumento do tráfego.

A origem é hospedada em um SignalR repositório no GitHub .

Transportes
SignalR dá suporte às seguintes técnicas para lidar com a comunicação em tempo real
(em ordem de fallback normal):
WebSockets
Eventos de enviados pelo servidor
Sondagem longa

SignalR escolhe automaticamente o melhor método de transporte que está dentro dos
recursos do servidor e do cliente.

Hubs
SignalR usa hubs para se comunicar entre clientes e servidores.

Um hub é um pipeline de alto nível que permite que um cliente e um servidor chamem
métodos uns nos outros. SignalR manipula a expedição entre limites do computador
automaticamente, permitindo que os clientes chamem métodos no servidor e vice-
versa. Você pode passar parâmetros fortemente tipado para métodos, o que permite a
associação de modelo. SignalR fornece dois protocolos de hub internos: um protocolo
de texto baseado em JSON e um protocolo binário baseado em MessagePack . O
MessagePack geralmente cria mensagens menores em comparação com ON JS. Os
navegadores mais antigos devem dar suporte ao nível 2 do XHR para fornecer
suporte ao protocolo MessagePack.

Os hubs chamam o código do lado do cliente enviando mensagens que contêm o nome
e os parâmetros do método do lado do cliente. 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 para ele os dados de
parâmetro desserializados.

Recursos adicionais
Introdução ao ASP.NET Core SignalR
Introdução ao SignalR ASP.NET Core
Plataformas com suporte
Hubs
Cliente JavaScript
SignalR ASP.NET Core plataformas com
suporte
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

Requisitos do sistema do servidor do


SignalRpara ASP.NET Core dá suporte a qualquer plataforma de servidor compatível
com ASP.NET Core.

Cliente JavaScript
O cliente JavaScript é executado na versão atualNode.js LTS (suporte a longo prazo) e
nos seguintes navegadores:

Navegador Versão

Apple Safari, incluindo iOS Atual†

Google Chrome, incluindo Android Atual†

Microsoft Edge Atual†

Mozilla Firefox Atual†

†Current refere-se à versão mais recente do navegador.

O cliente JavaScript não dá suporte ao Internet Explorer e a outros navegadores mais


antigos. O cliente pode ter um comportamento inesperado e erros em navegadores sem
suporte.

Cliente .NET
O cliente .NET é executado em qualquer plataforma compatível com ASP.NET Core. Por
exemplo, os desenvolvedores do Xamarin podem usar SignalR para criar aplicativos
Android usando o Xamarin.Android 8.4.0.1 e aplicativos iOS posteriores e posteriores
usando o Xamarin.iOS 11.14.0.4 e posterior.

Se o servidor executar o IIS, o transporte WebSockets exigirá o IIS 8.0 ou posterior em


Windows Server 2012 ou posterior. Outros transportes têm suporte em todas as
plataformas.
Cliente Java
O cliente Java dá suporte ao Java 8 e versões posteriores.

Clientes sem suporte


Os clientes a seguir estão disponíveis, mas são experimentais ou não oficiais. No
momento, os seguintes clientes não têm suporte e podem nunca ter suporte:

Cliente C++
Cliente Swift
Tutorial: Introdução ao ASP.NET Core
SignalR
Artigo • 02/12/2022 • 14 minutos para o fim da leitura

Este tutorial ensina os conceitos básicos da criação de um aplicativo em tempo real


usando SignalR. Você aprenderá como:

" Crie um projeto Web.


" Adicione a biblioteca de SignalR clientes.
" Criar um SignalR hub.
" Configure o projeto para usar 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 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar um projeto do aplicativo Web


Visual Studio

1. Inicie o Visual Studio 2022 e selecione Criar um novo projeto.

2. Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core


Aplicativo Web e, em seguida, selecione Avançar.

3. Na caixa de diálogo Configurar seu novo projeto , insira SignalRChat para


Nome do projeto. É importante nomear o chat do projetoSignalR, incluindo a
correspondência da capitalização, para que os namespaces correspondam
quando você copiar e colar o código de exemplo.

4. Selecione Avançar.

5. Na caixa de diálogo Informações adicionais , selecione .NET 6.0 (suporte a


longo prazo) e, em seguida, selecione Criar.

Adicionar a SignalR biblioteca de clientes


A SignalR biblioteca de servidores está incluída na estrutura compartilhada ASP.NET
Core. A biblioteca de clientes do JavaScript não é incluída automaticamente no projeto.
Para este tutorial, use o Gerenciador de Bibliotecas (LibMan) para obter a biblioteca de
clientes do unpkg . unpkg é uma rede de distribuição de conteúdo global rápida para
tudo no npm .

Visual Studio

No Gerenciador de Soluções, clique com o botão direito do mouse no projeto


e selecione Adicionar>Biblioteca do Lado do Cliente.
Na caixa de diálogo Adicionar Biblioteca Client-Side :
Selecionar unpkg para Provedor
Inserir @microsoft/signalr@latest para Biblioteca
Selecione Escolher arquivos específicos, expanda a pasta dist/browser e
selecione signalr.js e signalr.min.js .
Definir Local de Destino como wwwroot/js/signalr/
Selecione Instalar

O LibMan cria uma pasta wwwroot/js/signalr e copia os arquivos selecionados para


ele.

Criar um SignalR hub


Um hub é uma classe que funciona como um pipeline de alto nível que lida com a
comunicação entre cliente e servidor.

Na pasta Projeto de SignalRchat, crie uma pasta Hubs .


Na pasta Hubs , crie a ChatHub classe com o seguinte código:

C#

using Microsoft.AspNetCore.SignalR;

namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}

A ChatHub classe herda da SignalRHub classe . 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. SignalR O código é assíncrono para fornecer
escalabilidade máxima.

configurar SignalR
O SignalR servidor deve ser configurado para passar SignalR solicitações para SignalR.
Adicione o código realçado a seguir ao Program.cs arquivo .

C#

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();
O código realçado anterior adiciona SignalR ao ASP.NET Core sistemas de injeção e
roteamento de dependência.

Adicionar SignalR código do cliente


Substitua o conteúdo em Pages/Index.cshtml pelo seguinte código:

CSHTML

@page
<div class="container">
<div class="row">&nbsp;</div>
<div class="row">
<div class="col-2">User</div>
<div class="col-4"><input type="text" id="userInput" />
</div>
</div>
<div class="row">
<div class="col-2">Message</div>
<div class="col-4"><input type="text" id="messageInput" />
</div>
</div>
<div class="row">&nbsp;</div>
<div class="row">
<div class="col-6">
<input type="button" id="sendButton" value="Send
Message" />
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>

A marcação anterior:
Cria caixas de texto e um botão enviar.
Cria uma lista com id="messagesList" para exibir mensagens recebidas do
SignalR hub.
Inclui referências de script para SignalR e o código do chat.js aplicativo é
criado na próxima etapa.
Na pasta wwwroot/js , crie um chat.js arquivo com o seguinte código:

JavaScript

"use strict";

var connection = new


signalR.HubConnectionBuilder().withUrl("/chatHub").build();

//Disable the send button until connection is established.


document.getElementById("sendButton").disabled = true;

connection.on("ReceiveMessage", function (user, message) {


var li = document.createElement("li");
document.getElementById("messagesList").appendChild(li);
// We can assign user-supplied strings to an element's textContent
because it
// is not interpreted as markup. If you're assigning in any other
way, you
// should be aware of possible script injection concerns.
li.textContent = `${user} says ${message}`;
});

connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});

document.getElementById("sendButton").addEventListener("click",
function (event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, message).catch(function
(err) {
return console.error(err.toString());
});
event.preventDefault();
});

O JavaScript 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.

Execute o aplicativo
Visual Studio
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.

 Dica

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 da direcionada. Nesse caso, a referência a

esse arquivo não funcionará e ocorrerá um erro 404 no console.

Se você receber o erro ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY no


Chrome, execute estes comandos para atualizar seu certificado de
desenvolvimento:
CLI do .NET

dotnet dev-certs https --clean


dotnet dev-certs https --trust

Publicar no Azure
Para obter informações sobre como implantar no Azure, confira Início Rápido: Implantar
um aplicativo Web ASP.NET.
Tutorial: Introdução ao ASP.NET Core
SignalR usando TypeScript e Webpack
Artigo • 04/01/2023 • 34 minutos para o fim da leitura

Por Sébastien Sougnez e Scott Addie

Este tutorial demonstra o uso do Webpack em um aplicativo Web ASP.NET Core


SignalR para agrupar e criar um cliente escrito no TypeScript . O Webpack habilita os
desenvolvedores a agrupar e criar recursos de um aplicativo Web do lado do cliente.

Neste tutorial, você aprenderá como:

" Criar um aplicativo ASP.NET Core SignalR


" Configurar o SignalR servidor
" Configurar um pipeline de build usando o Webpack
" Configurar o SignalR cliente TypeScript
" Habilitar a comunicação entre o cliente e o servidor

Exibir ou baixar código de exemplo (como baixar)

Pré-requisitos
Node.js com npm

Visual Studio

Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do


ASP.NET.

Criar o aplicativo Web do ASP.NET Core


Visual Studio

Por padrão, o Visual Studio usa a versão do npm encontrada no diretório de


instalação. Para configurar o Visual Studio para procurar npm na variável de PATH
ambiente:

1. Inicie o Visual Studio. Na janela inicial, selecione Continuar sem código.


2. Navegue até projetos deopções> de ferramentas>e ferramentas web
externasdegerenciamento> depacotes web web de soluções>.

3. Selecione a $(PATH) entrada na lista. Selecione a seta para cima para mover a
entrada para a segunda posição na lista e selecione OK:

Para criar um novo aplicativo Web ASP.NET Core:

1. Use a opçãode menu Novo>Projeto de Arquivo> e escolha o modelo


ASP.NET Core Vazio. Selecione Avançar.
2. Nomeie o projeto SignalRWebpack e selecione Criar.
3. Selecione .NET 6.0 (Long-term support) na lista suspensa da Estrutura .
Selecione Criar.

Adicione o pacote NuGet Microsoft.TypeScript.MSBuild ao projeto:

1. Em Gerenciador de Soluções, clique com o botão direito do mouse no nó do


projeto e selecione Gerenciar Pacotes NuGet. Na guia Procurar , pesquise
Microsoft.TypeScript.MSBuild e selecione Instalar à direita para instalar o

pacote.

O Visual Studio adiciona o pacote NuGet no nó Dependências em Gerenciador de


Soluções, habilitando a compilação typeScript no projeto.

Configurar o servidor
Nesta seção, você configurará o aplicativo Web ASP.NET Core para enviar e receber
SignalR mensagens.

1. In Program.cs , call AddSignalR:

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();

2. Novamente, em Program.cs , chamar UseDefaultFiles e UseStaticFiles:

C#

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

O código anterior permite que o servidor localize e atenda ao index.html arquivo.


O arquivo é servido se o usuário insere sua URL completa ou a URL raiz do
aplicativo Web.

3. Crie um novo diretório nomeado Hubs na raiz SignalRWebpack/ do projeto, para a


SignalR classe hub.

4. Crie um novo arquivo, Hubs/ChatHub.cs com o seguinte código:

C#

using Microsoft.AspNetCore.SignalR;

namespace SignalRWebpack.Hubs;

public class ChatHub : Hub


{
public async Task NewMessage(long 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 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 em SendAsyncClients.All. As mensagens recebidas são
enviadas a todos os clientes conectados ao hub.

5. Adicione a seguinte using instrução na parte superior Program.cs para resolver a


ChatHub referência:

C#

using SignalRWebpack.Hubs;

6. In Program.cs , mapeie a /hub rota para o ChatHub hub. Substitua o código exibido
Hello World! pelo seguinte código:

C#

app.MapHub<ChatHub>("/hub");

Configurar o cliente
Nesta seção, você criará um projeto Node.js para converter o TypeScript em
JavaScript e agrupar recursos do lado do cliente, incluindo HTML e CSS, usando o
Webpack.

1. Execute o seguinte comando na raiz do projeto para criar um package.json


arquivo:

Console

npm init -y

2. Adicione a propriedade realçada ao package.json arquivo e salve as alterações de


arquivo:

JSON

{
"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:

Console

npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-


css-extract-plugin ts-loader typescript webpack webpack-cli

A -E opção desabilita o comportamento padrão do npm de gravar operadores de


intervalo de controle de versão semântico para package.json . Por exemplo,
"webpack": "5.70.0" é usado em vez de "webpack": "^5.70.0" . Essa opção evita

atualizações não intencionais para versões de pacote mais recentes.

Para obter mais informações, consulte a documentação de instalação do npm .

4. Substitua a scripts propriedade do package.json arquivo pelo seguinte código:

JSON

"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},

Os seguintes scripts são definidos:

build : agrupa os recursos do lado do cliente no modo de desenvolvimento e

observa as alterações de arquivo. O observador de arquivos faz com que o


lote se regenere toda vez que um arquivo de projeto é alterado. A opção
mode desabilita otimizações de produção, como tree shaking e minificação.

usar build somente em desenvolvimento.


release : agrupa os recursos do lado do cliente no modo de produção.

publish : executa o script release para agrupar recursos do lado do cliente


no modo de produção. Ele chama o comando de publicação da CLI do .NET
para publicar o aplicativo.

5. Crie um arquivo nomeado webpack.config.js na raiz do projeto, com o seguinte


código:

JavaScript

const path = require("path");


const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

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(),
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
filename: "css/[name].[chunkhash].css",
}),
],
};

O arquivo anterior configura o processo de compilação do Webpack:

A output propriedade substitui o valor padrão de dist . Em vez disso, o


pacote é emitido no wwwroot diretório.
A resolve.extensions matriz inclui .js importar o JavaScript do SignalR
cliente.
6. Copie o src diretório do projeto de exemplo para a raiz do projeto. O src
diretório contém os seguintes arquivos:

index.html , que define a marcação clichê da página inicial:

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ASP.NET Core SignalR with TypeScript and
Webpack</title>
</head>
<body>
<div id="divMessages" class="messages"></div>
<div class="input-zone">
<label id="lblMessage" for="tbMessage">Message:</label>
<input id="tbMessage" class="input-zone-input" type="text"
/>
<button id="btnSend">Send</button>
</div>
</body>
</html>

css/main.css , que fornece estilos CSS para a página inicial:

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;
}

tsconfig.json , que configura o compilador TypeScript para produzir


JavaScript compatível com ECMAScript 5:

JSON

{
"compilerOptions": {
"target": "es5"
}
}

index.ts :

TypeScript

import * as signalR from "@microsoft/signalr";


import "./css/main.css";

const divMessages: HTMLDivElement =


document.querySelector("#divMessages");
const tbMessage: HTMLInputElement =
document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement =
document.querySelector("#btnSend");
const username = new Date().getTime();

const connection = new signalR.HubConnectionBuilder()


.withUrl("/hub")
.build();

connection.on("messageReceived", (username: string, message:


string) => {
const m = document.createElement("div");

m.innerHTML = `<div class="message-author">${username}</div>


<div>${message}</div>`;
divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});

connection.start().catch((err) => document.write(err));

tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {


if (e.key === "Enter") {
send();
}
});

btnSend.addEventListener("click", send);

function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => (tbMessage.value = ""));
}

O código anterior recupera referências a elementos DOM e anexa dois


manipuladores de eventos:
keyup : é acionado quando o usuário digita na tbMessage caixa de texto e

chama a send função quando o usuário pressiona a tecla Enter .


click : é acionado quando o usuário seleciona o botão Enviar e a função

de chamadas send é chamada.

A classe HubConnectionBuilder cria um novo construtor para configurar a


conexão do servidor. A função withUrl configura a URL do hub.

SignalR permite a troca de mensagens entre um cliente e um servidor. Cada


mensagem tem um nome específico. Por exemplo, mensagens com o nome
messageReceived podem executar a lógica responsável por exibir a nova
mensagem na zona de mensagens. É possível escutar uma mensagem
específica por meio da função on . Qualquer número de nomes de mensagem
pode ser ouvido. Também é possível passar parâmetros para a mensagem,
como o nome do autor e o conteúdo da mensagem recebida. Quando o
cliente recebe a mensagem, um novo elemento div é criado com o nome do
autor e o conteúdo da mensagem em seu atributo innerHTML . Ele é
adicionado ao elemento principal div que exibe as mensagens.

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.

7. Execute o seguinte comando na raiz do projeto:

Console

npm i @microsoft/signalr @types/node

O comando anterior é instalado:

O SignalR cliente TypeScript , que permite que o cliente envie mensagens


para o servidor.
As definições de tipo TypeScript para Node.js, que permite a verificação em
tempo de compilação de tipos de Node.js.

Testar o aplicativo
Confirme se o aplicativo funciona com as seguintes etapas:

Visual Studio

1. Execute o Webpack no release modo. 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.

Console

npm run release

Esse comando gera os ativos do lado do cliente a serem atendidos ao


executar o aplicativo. Os ativos são colocados na wwwroot pasta.

O Webpack concluiu as seguintes tarefas:

Limpou o conteúdo do wwwroot diretório.


Converteu o TypeScript em JavaScript em um processo conhecido como
transpilação.
Mangled o JavaScript gerado para reduzir o tamanho do arquivo em um
processo conhecido como minificação.
Copiou os arquivos JavaScript, CSS e HTML processados do src wwwroot
diretório.
Injetou os seguintes elementos no wwwroot/index.html arquivo:
Uma <link> marca, fazendo referência ao wwwroot/main.<hash>.css
arquivo. Essa marca é colocada imediatamente antes do fim da marca
</head> .

Uma <script> marca, fazendo referência ao arquivo minificado


wwwroot/main.<hash>.js . Essa marca é colocada imediatamente antes

do fim da marca </body> .

2. Selecione Iniciar de Depuração>sem depuração para iniciar o aplicativo em


um navegador sem anexar o depurador. O wwwroot/index.html arquivo é
servido em https://localhost:<port> .

Se você receber erros de compilação, tente fechar e reabrir a solução.

3. Abra outra instância de navegador (qualquer navegador) e cole a URL na barra


de endereços.

4. Escolha qualquer navegador, digite algo na caixa de texto Mensagem e


selecione o botão Enviar . O nome de usuário exclusivo e a mensagem são
exibidas em ambas as páginas instantaneamente.

Recursos adicionais
SignalR ASP.NET Core cliente JavaScript
Usar hubs no ASP.NET Core SignalR
Usar ASP.NET Core SignalR com Blazor
Artigo • 28/11/2022 • 68 minutos para o fim da leitura

Este tutorial ensina os conceitos básicos da criação de um aplicativo em tempo real


usando SignalR com Blazoro .

Saiba como:

" Criar um Blazor projeto


" Adicionar a SignalR biblioteca de clientes
" Adicionar um SignalR hub
" Adicionar SignalR serviços e um ponto de extremidade para o SignalR hub
" Adicionar Razor código de componente para chat

No final deste tutorial, você terá um aplicativo de chat funcional.

Pré-requisitos
Visual Studio

Visual Studio 2022 ou posterior com a carga de trabalho ASP.NET e


desenvolvimento para a Web
SDK do .NET 6.0

Aplicativo de exemplo
O download do aplicativo de chat de exemplo do tutorial não é necessário para este
tutorial. O aplicativo de exemplo é o aplicativo final e funcional produzido seguindo as
etapas deste tutorial.

Exibir ou baixar o código de exemplo

Criar um Blazor Server aplicativo


Siga as diretrizes para sua escolha de ferramentas:

Visual Studio
7 Observação

O Visual Studio 2022 ou posterior e o SDK do .NET Core 6.0.0 ou posterior são
necessários.

1. Criar um novo projeto.

2. Selecione o Blazor Server Modelo de aplicativo. Selecione Avançar.

3. Digite BlazorServerSignalRApp o campo Nome do projeto. Confirme se a


entrada Local está correta ou forneça um local para o projeto. Selecione
Avançar.

4. Selecione Criar.

Adicionar a SignalR biblioteca de clientes


Visual Studio

1. Em Gerenciador de Soluções, clique com o botão direito do mouse no


BlazorServerSignalRApp projeto e selecione Gerenciar Pacotes NuGet.

2. Na caixa de diálogo Gerenciar Pacotes NuGet, confirme se a origem do


pacote está definida como nuget.org .

3. Com Procurar selecionado, digite Microsoft.AspNetCore.SignalR.Client na


caixa de pesquisa.

4. Nos resultados da pesquisa, selecione o


Microsoft.AspNetCore.SignalR.Client pacote. Defina a versão para
corresponder à estrutura compartilhada do aplicativo. Selecione Instalar.

5. Se a caixa de diálogo Visualizar Alterações for exibida, selecione OK.

6. Se a caixa de diálogo Aceitação da Licença for exibida, selecione Aceito se


você concordar com os termos de licença.

Adicionar um SignalR hub


Crie uma Hubs pasta (plural) e adicione a seguinte ChatHub classe ( Hubs/ChatHub.cs ):

C#

using Microsoft.AspNetCore.SignalR;

namespace BlazorServerSignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}

Adicionar serviços e um ponto de extremidade


para o SignalR hub
1. Abra o arquivo Program.cs .

2. Adicione os namespaces para Microsoft.AspNetCore.ResponseCompression e a


ChatHub classe à parte superior do arquivo:

C#

using Microsoft.AspNetCore.ResponseCompression;
using BlazorServerSignalRApp.Server.Hubs;

3. Adicione serviços middleware de compactação de resposta a Program.cs :

C#

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});

4. Em Program.cs :
Use o Middleware de Compactação de Resposta na parte superior da
configuração do pipeline de processamento.
Entre os pontos de extremidade para mapear o Blazor hub e o fallback do
lado do cliente, adicione um ponto de extremidade para o hub.

C#

app.UseResponseCompression();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToPage("/_Host");

app.Run();

Adicionar Razor código de componente para


chat
1. Abra o arquivo Pages/Index.razor .

2. Substitua a marcação pelo seguinte código:

razor

@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable

<PageTitle>Index</PageTitle>

<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>

@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();

hubConnection.On<string, string>("ReceiveMessage", (user,


message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});

await hubConnection.StartAsync();
}

private async Task Send()


{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput,
messageInput);
}
}

public bool IsConnected =>


hubConnection?.State == HubConnectionState.Connected;

public async ValueTask DisposeAsync()


{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}

7 Observação

Desabilite o middleware de compactação de resposta no Development ambiente ao


usar Recarga Dinâmica. Para obter mais informações, consulte ASP.NET Core
BlazorSignalR diretrizes.

Executar o aplicativo
Siga as diretrizes para suas ferramentas:

Visual Studio

1. Pressione F5 para executar o aplicativo com depuração ou Ctrl + F5

(Windows)/ ⌘ + F5 (macOS) para executar o aplicativo sem depuração.

2. 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.

3. Escolha qualquer navegador, insira um nome e uma mensagem e selecione o


botão para enviar a mensagem. O nome e a mensagem são exibidos em
ambas as páginas instantaneamente:

Citações: Star Trek VI: O País ©Desconhecido 1991 Paramount


Próximas etapas
Neste tutorial, você aprendeu a:

" Criar um Blazor projeto


" Adicionar a SignalR biblioteca de clientes
" Adicionar um SignalR hub
" Adicionar SignalR serviços e um ponto de extremidade para o SignalR hub
" Adicionar Razor código de componente para chat

Para saber mais sobre como criar Blazor aplicativos, consulte a Blazor documentação:

BlazorASP.NET Core

autenticação de token de portador com Identity Eventos de Servidor, WebSockets


e Server-Sent

Recursos adicionais
Hubs seguros SignalR para aplicativos hospedados Blazor WebAssembly
Visão geral do ASP.NET Core SignalR
SignalR negociação entre origens para autenticação
SignalR Configuração
Depurar ASP.NET Core Blazor WebAssembly
Diretrizes de mitigação de ameaças para ASP.NET Core Blazor Server
Blazor exemplos do repositório GitHub (dotnet/blazor-samples)
Usar hubs no SignalR para ASP.NET Core
Artigo • 05/01/2023 • 31 minutos para o fim da leitura

Por Rachel Appel e Kevin Griffin

A SignalR API hubs permite que clientes conectados chamem métodos no servidor. O
servidor define métodos que são chamados do cliente e o cliente define métodos que
são chamados do servidor. SignalR cuida de tudo o que é necessário para tornar
possível a comunicação de cliente para servidor e servidor para cliente em tempo real.

Configurar SignalR hubs


Para registrar os serviços exigidos pelos SignalR hubs, chame AddSignalR em
Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

Para configurar SignalR pontos de extremidade, chame MapHub, também em


Program.cs :

C#

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

Criar e usar hubs


Crie um hub declarando uma classe que herda de Hub. Adicione public métodos à
classe para torná-los chamáveis de clientes:

C#

public class ChatHub : Hub


{
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
}

7 Observação

Os hubs são transitórios:

Não armazene o estado em uma propriedade da classe hub. Cada chamada


de método de hub é executada em uma nova instância do hub.
Use await ao chamar métodos assíncronos que dependem do hub
permanecer ativo. Por exemplo, um método como
Clients.All.SendAsync(...) pode falhar se for chamado sem await e o

método hub for concluído antes SendAsync de ser concluído.

O objeto Context
A Hub classe inclui uma Context propriedade que contém as seguintes propriedades
com informações sobre a conexão:

Propriedade Descrição

ConnectionId Obtém a ID exclusiva para a conexão, atribuída por SignalR. Há uma ID de


conexão para cada conexão.

UserIdentifier Obtém o identificador do usuário. Por padrão, SignalR usa o


ClaimTypes.NameIdentifierClaimsPrincipal do associado à conexão como o
identificador de usuário.

User Obtém o ClaimsPrincipal associado ao usuário atual.

Items Obtém uma coleção chave/valor que pode ser usada para compartilhar
dados dentro do escopo dessa conexão. Os dados podem ser armazenados
nesta coleção e persistirão para a conexão entre diferentes invocações de
método de hub.

Features Obtém a coleção de recursos disponíveis na conexão. Por enquanto, essa


coleção não é necessária na maioria dos cenários, portanto, ainda não está
documentada em detalhes.

ConnectionAborted Obtém um CancellationToken que notifica quando a conexão é anulada.

Hub.Context também contém os seguintes métodos:

Método Descrição
Método Descrição

GetHttpContext Retorna o HttpContext para a conexão ou null se a conexão não estiver


associada a uma solicitação HTTP. Para conexões HTTP, use esse método para
obter informações como cabeçalhos HTTP e cadeias de caracteres de consulta.

Abort Anula a conexão.

O objeto Clients
A Hub classe inclui uma Clients propriedade que contém as seguintes propriedades para
comunicação entre o servidor e o cliente:

Propriedade Descrição

All Chama um método em todos os clientes conectados

Caller Chama um método no cliente que invocou o método hub

Others Chama um método em todos os clientes conectados, exceto o cliente que invocou
o método

Hub.Clients também contém os seguintes métodos:

Método Descrição

AllExcept Chama um método em todos os clientes conectados, exceto para as conexões


especificadas

Client Chama um método em um cliente conectado específico

Clients Chama um método em clientes conectados específicos

Group Chama um método em todas as conexões no grupo especificado

GroupExcept Chama um método em todas as conexões no grupo especificado, exceto as


conexões especificadas

Groups Chama um método em vários grupos de conexões

OthersInGroup Chama um método em um grupo de conexões, excluindo o cliente que invocou


o método hub

User Chama um método em todas as conexões associadas a um usuário específico

Users Chama um método em todas as conexões associadas aos usuários especificados


Cada propriedade ou método nas tabelas anteriores retorna um objeto com um
SendAsync método . O SendAsync método recebe o nome do método cliente para
chamar e quaisquer parâmetros.

O objeto retornado pelos Client métodos e Caller também contém um InvokeAsync


método , que pode ser usado para aguardar um resultado do cliente.

Enviar mensagens para clientes


Para fazer chamadas para clientes específicos, use as propriedades do Clients objeto .
No exemplo a seguir, há três métodos de hub:

SendMessage envia uma mensagem para todos os clientes conectados, usando


Clients.All .

SendMessageToCaller envia uma mensagem de volta para o chamador, usando


Clients.Caller .

SendMessageToGroup envia uma mensagem para todos os clientes no SignalR

Users grupo.

C#

public async Task SendMessage(string user, string message)


=> await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)


=> await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)


=> await Clients.Group("SignalR Users").SendAsync("ReceiveMessage",
user, message);

Hubs fortemente tipado


Uma desvantagem de usar SendAsync é que ele depende de uma cadeia de caracteres
para especificar o método cliente a ser chamado. Isso deixará o código aberto para
erros de runtime se o nome do método estiver escrito incorretamente ou ausente do
cliente.

Uma alternativa ao uso SendAsync é digitar fortemente a Hub classe com Hub<T>. No
exemplo a seguir, o ChatHub método cliente foi extraído para uma interface chamada
IChatClient :
C#

public interface IChatClient


{
Task ReceiveMessage(string user, string message);
}

Essa interface pode ser usada para refatorar o exemplo anterior ChatHub para ser
fortemente tipado:

C#

public class StronglyTypedChatHub : Hub<IChatClient>


{
public async Task SendMessage(string user, string message)
=> await Clients.All.ReceiveMessage(user, message);

public async Task SendMessageToCaller(string user, string message)


=> await Clients.Caller.ReceiveMessage(user, message);

public async Task SendMessageToGroup(string user, string message)


=> await Clients.Group("SignalR Users").ReceiveMessage(user,
message);
}

Usar Hub<IChatClient> habilita a verificação em tempo de compilação dos métodos do


cliente. Isso impede problemas causados pelo uso de cadeias de caracteres, pois Hub<T>
só pode fornecer acesso aos métodos definidos na interface. Usar um fortemente tipado
Hub<T> desabilita a capacidade de usar SendAsync .

7 Observação

O Async sufixo não é retirado dos nomes de método. A menos que um método de
cliente seja definido com .on('MyMethodAsync') , não use MyMethodAsync como o
nome.

Resultados do cliente
Além de fazer chamadas para clientes, o servidor pode solicitar um resultado de um
cliente. Isso exige que o servidor use ISingleClientProxy.InvokeAsync e que o cliente
retorne um resultado de seu .On manipulador.
Há duas maneiras de usar a API no servidor, a primeira é chamar Client(...) ou Caller
na Clients propriedade em um método hub:

C#

public class ChatHub : Hub


{
public async Task<string> WaitForMessage(string connectionId)
{
var message = await Clients.Client(connectionId).InvokeAsync<string>
(
"GetMessage");
return message;
}
}

A segunda maneira é chamar Client(...) em uma instância de IHubContext<T>:

C#

async Task SomeMethod(IHubContext<MyHub> context)


{
string result = await
context.Clients.Client(connectionID).InvokeAsync<string>(
"GetMessage");
}

Hubs fortemente tipados também podem retornar valores de métodos de interface:

C#

public interface IClient


{
Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>


{
public async Task<string> WaitForMessage(string connectionId)
{
string message = await Clients.Client(connectionId).GetMessage();
return message;
}
}

Os clientes retornam resultados em seus .On(...) manipuladores, conforme mostrado


abaixo:
Cliente .NET

C#

hubConnection.On("GetMessage", async () =>


{
Console.WriteLine("Enter message:");
var message = await Console.In.ReadLineAsync();
return message;
});

Cliente Typescript

TypeScript

hubConnection.on("GetMessage", async () => {


let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("message");
}, 100);
});
return promise;
});

Alterar o nome de um método de hub


Por padrão, um nome de método de hub de servidor é o nome do método .NET. Para
alterar esse comportamento padrão para um método específico, use o atributo
HubMethodName . O cliente deve usar esse nome em vez do nome do método .NET ao
invocar o método :

C#

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
=> await Clients.User(user).SendAsync("ReceiveMessage", user, message);

Injetar serviços em um hub


Os construtores de hub podem aceitar serviços de DI como parâmetros, que podem ser
armazenados em propriedades na classe para uso em um método de hub.

Ao injetar vários serviços para diferentes métodos de hub ou como uma maneira
alternativa de escrever código, os métodos de hub também podem aceitar serviços da
DI. Por padrão, os parâmetros do método hub são inspecionados e resolvidos a partir
da DI, se possível.

C#

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub


{
public Task SendMessage(string user, string message, IDatabaseService
dbService)
{
var userName = dbService.GetUserName(user);
return Clients.All.SendAsync("ReceiveMessage", userName, message);
}
}

Se a resolução implícita de parâmetros de serviços não for desejada, desabilite-a com


DisableImplicitFromServicesParameters. Para especificar explicitamente quais
parâmetros são resolvidos de DI em métodos de hub, use a opção
DisableImplicitFromServicesParameters e use o [FromServices] atributo ou um atributo
personalizado que implementa nos parâmetros IFromServiceMetadata de método de
hub que devem ser resolvidos a partir da DI.

C#

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub


{
public Task SendMessage(string user, string message,
[FromServices] IDatabaseService dbService)
{
var userName = dbService.GetUserName(user);
return Clients.All.SendAsync("ReceiveMessage", userName, message);
}
}

7 Observação
Esse recurso usa IServiceProviderIsService, que opcionalmente é implementado
por implementações de DI. Se o contêiner de DI do aplicativo não der suporte a
esse recurso, não há suporte para injetar serviços em métodos de hub.

Manipular eventos para uma conexão


A SignalR API de Hubs fornece os OnConnectedAsync métodos virtuais e
OnDisconnectedAsync para gerenciar e rastrear conexões. Substitua o OnConnectedAsync
método virtual para executar ações quando um cliente se conectar ao hub, como
adicioná-lo a um grupo:

C#

public override async Task OnConnectedAsync()


{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}

Substitua o OnDisconnectedAsync método virtual para executar ações quando um cliente


se desconectar. Se o cliente se desconectar intencionalmente, como chamando
connection.stop() , o exception parâmetro será definido null como . No entanto, se o
cliente se desconectar devido a um erro, como uma falha de rede, o exception
parâmetro conterá uma exceção que descreve a falha:

C#

public override async Task OnDisconnectedAsync(Exception? exception)


{
await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsync não precisa ser chamado no OnDisconnectedAsync, ele é


tratado automaticamente para você.

Tratar erros
Exceções geradas em métodos de hub são enviadas ao cliente que invocou o método .
No cliente JavaScript, o invoke método retorna um JavaScript Promise . Os clientes
podem anexar um catch manipulador à promessa retornada ou usar/ catch try com
para lidar com async / await exceções:
JavaScript

try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}

As conexões não são fechadas quando um hub gera uma exceção. Por padrão, SignalR
retorna uma mensagem de erro genérica para o cliente, conforme mostrado no
exemplo a seguir:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred


invoking 'SendMessage' 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 disparada quando a conexão de
banco de dados falha. SignalR não expõe essas mensagens de erro detalhadas por
padrão como uma medida de segurança. Para obter mais informações sobre por que os
detalhes da exceção são suprimidos, consulte Considerações de segurança no ASP.NET
Core SignalR.

Se uma condição excepcional precisar ser propagada para o cliente, use a HubException
classe . Se um HubException for gerado em um método de hub, SignalRenviará toda a
mensagem de exceção para o cliente, sem modificação:

C#

public Task ThrowException()


=> throw new HubException("This error will be sent to the client!");

7 Observação

SignalR apenas envia a 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 adicionais
Exibir ou baixar código de exemplo (como baixar)
Visão geral do ASP.NET Core SignalR
SignalR ASP.NET Core cliente JavaScript
Publicar um aplicativo de ASP.NET Core SignalR para Serviço de Aplicativo do
Azure
Enviar mensagens de fora de um hub
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

O SignalR hub é a abstração principal para enviar mensagens para clientes conectados
ao SignalR servidor. Também é possível enviar mensagens de outros locais em seu
aplicativo usando o IHubContext serviço. Este artigo explica como acessar um
SignalR IHubContext para enviar notificações para clientes de fora de um hub.

7 Observação

O IHubContext é para enviar notificações para clientes, ele não é usado para
chamar métodos no Hub .

Exibir ou baixar código de exemplo (como baixar)

Obter uma instância de IHubContext


Em ASP.NET CoreSignalR, você pode acessar uma instância por meio de injeção de
IHubContext dependência. Você pode injetar uma instância de IHubContext um

controlador, middleware ou outro serviço DI. Use a instância para enviar mensagens aos
clientes.

7 Observação

Isso difere do ASP.NET 4.x SignalR que usou o GlobalHost para fornecer acesso ao
IHubContext . ASP.NET Core tem uma estrutura de injeção de dependência que
remove a necessidade desse singleton global.

Injetar uma instância de IHubContext em um controlador


Você pode injetar uma instância de IHubContext um controlador adicionando-a ao
construtor:

C#

public class HomeController : Controller


{
private readonly IHubContext<NotificationHub> _hubContext;

public HomeController(IHubContext<NotificationHub> hubContext)


{
_hubContext = hubContext;
}
}

Com acesso a uma instância de métodos de cliente de IHubContext chamada como se


você estivesse no próprio hub:

C#

public async Task<IActionResult> Index()


{
await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at:
{DateTime.Now}");
return View();
}

Obter uma instância do IHubContext middleware


Acesse o IHubContext pipeline do middleware da seguinte maneira:

C#

app.Use(async (context, next) =>


{
var hubContext = context.RequestServices
.GetRequiredService<IHubContext<ChatHub>>();
//...

if (next != null)
{
await next.Invoke();
}
});

7 Observação

Quando os métodos de cliente são chamados de fora da Hub classe, não há


nenhum chamador associado à invocação. Portanto, não há acesso ao
ConnectionId , Caller e Others propriedades.

Obter uma instância do IHubContext IHost


Acessar um IHubContext host da Web é útil para integrar com áreas fora do ASP.NET
Core, por exemplo, usando estruturas de injeção de dependência de terceiros:

C#

public class Program


{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var hubContext =
host.Services.GetService(typeof(IHubContext<ChatHub>));
host.Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseStartup<Startup>();
});
}

Injetar um HubContext fortemente tipado


Para injetar um HubContext fortemente tipado, verifique se o Hub herda de Hub<T> .
Injete-o usando a IHubContext<THub, T> interface em vez de IHubContext<THub> .

C#

public class ChatController : Controller


{
public IHubContext<ChatHub, IChatClient> _strongChatHubContext { get; }

public ChatController(IHubContext<ChatHub, IChatClient> chatHubContext)


{
_strongChatHubContext = chatHubContext;
}

public async Task SendMessage(string user, string message)


{
await _strongChatHubContext.Clients.All.ReceiveMessage(user,
message);
}
}

Consulte hubs fortemente tipado para obter mais informações.

Usar IHubContext em código genérico


Uma instância injetada IHubContext<THub> pode ser convertida IHubContext sem um
tipo genérico Hub especificado.

C#

class MyHub : Hub


{ }

class MyOtherHub : Hub


{ }

app.Use(async (context, next) =>


{
var myHubContext = context.RequestServices
.GetRequiredService<IHubContext<MyHub>>();
var myOtherHubContext = context.RequestServices
.GetRequiredService<IHubContext<MyOtherHub>>();
await CommonHubContextMethod((IHubContext)myHubContext);
await CommonHubContextMethod((IHubContext)myOtherHubContext);

await next.Invoke();
}

async Task CommonHubContextMethod(IHubContext context)


{
await context.Clients.All.SendAsync("clientMethod", new Args());
}

Isso é útil quando:

Escrevendo bibliotecas que não têm uma referência ao tipo específico Hub que o
aplicativo está usando.
Escrever código que é genérico e pode se aplicar a várias implementações
diferentes Hub

Recursos adicionais
Introdução ao ASP.NET Core SignalR
Usar hubs no ASP.NET Core SignalR
Publicar um aplicativo ASP.NET Core SignalR para Serviço de Aplicativo do Azure
Gerenciar usuários e grupos em SignalR
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

Por Brennan Conroy

SignalR permite que as mensagens sejam enviadas a todas as conexões associadas a um


usuário específico, bem como a grupos nomeados de conexões.

Exibir ou baixar código de exemplo (como baixar)

Usuários em SignalR
Um único usuário pode SignalR ter várias conexões com um aplicativo. Por exemplo, um
usuário pode estar conectado na área de trabalho, bem como no telefone. Cada
dispositivo tem uma conexão separada SignalR , mas todos estão associados ao mesmo
usuário. Se uma mensagem for enviada ao usuário, todas as conexões associadas a esse
usuário receberão a mensagem. O identificador de usuário para uma conexão pode ser
acessado pela Context.UserIdentifier propriedade no hub.

Por padrão, SignalR usa o ClaimTypes.NameIdentifier do ClaimsPrincipal associado à


conexão como o identificador de usuário. Para personalizar esse comportamento,
consulte Usar declarações para personalizar o tratamento de identidade.

Envie uma mensagem para um usuário específico passando o identificador de usuário


para a User função em um método hub, conforme mostrado no exemplo a seguir:

7 Observação

O identificador de usuário diferencia maiúsculas de minúsculas.

C#

public Task SendPrivateMessage(string user, string message)


{
return Clients.User(user).SendAsync("ReceiveMessage", message);
}

Grupos em SignalR
Um grupo é uma coleção de conexões associadas a um nome. As mensagens podem ser
enviadas para todas as conexões em um grupo. Os grupos são a maneira recomendada
de enviar para uma conexão ou várias conexões porque os grupos são gerenciados pelo
aplicativo. Uma conexão pode ser membro de vários grupos. Os grupos são ideais para
algo como um aplicativo de chat, em que cada sala pode ser representada como um
grupo. As conexões são adicionadas ou removidas de grupos por meio dos métodos e
RemoveFromGroupAsync dos AddToGroupAsync métodos.

C#

public async Task AddToGroup(string groupName)


{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);

await Clients.Group(groupName).SendAsync("Send", $"


{Context.ConnectionId} has joined the group {groupName}.");
}

public async Task RemoveFromGroup(string groupName)


{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);

await Clients.Group(groupName).SendAsync("Send", $"


{Context.ConnectionId} has left the group {groupName}.");
}

A associação de grupo não é preservada quando uma conexão se reconecta. A conexão


precisa voltar ao grupo quando for restabeleceda. Não é possível contar os membros de
um grupo, pois essas informações não estarão disponíveis se o aplicativo for
dimensionado para vários servidores.

Para proteger o acesso aos recursos ao usar grupos, use a funcionalidade de


autenticação e autorização em ASP.NET Core. Se um usuário for adicionado a um grupo
somente quando as credenciais forem válidas para esse grupo, as mensagens enviadas a
esse grupo serão somente para usuários autorizados. No entanto, os grupos não são
um recurso de segurança. As declarações de autenticação têm recursos que os grupos
não têm, como expiração e revogação. Se a permissão de um usuário para acessar o
grupo for revogada, o aplicativo deverá remover o usuário do grupo explicitamente.

7 Observação

Os nomes de grupo diferenciam maiúsculas de minúsculas.

Recursos adicionais
Introdução ao ASP.NET Core SignalR
Usar hubs no ASP.NET Core SignalR
Publicar um aplicativo ASP.NET Core SignalR para Serviço de Aplicativo do Azure
SignalR Considerações de design de API
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

Por Andrew Stanton-Nurse

Este artigo fornece diretrizes para a criação SignalRde APIs baseadas em criação.

Usar parâmetros de objeto personalizados para


garantir a compatibilidade com versões
anteriores
Adicionar parâmetros a um SignalR método hub (no cliente ou no servidor) é uma
alteração significativa. Isso significa que os clientes/servidores mais antigos receberão
erros quando tentarem invocar o método sem o número apropriado de parâmetros. No
entanto, adicionar propriedades a um parâmetro de objeto personalizado não é uma
alteração significativa. Isso pode ser usado para criar APIs compatíveis que são
resilientes a alterações no cliente ou no servidor.

Por exemplo, considere uma API do lado do servidor como a seguinte:

C#

public async Task<string> GetTotalLength(string param1)


{
return param1.Length;
}

O cliente JavaScript chama esse método usando invoke da seguinte maneira:

TypeScript

connection.invoke("GetTotalLength", "value1");

Se posteriormente você adicionar um segundo parâmetro ao método de servidor, os


clientes mais antigos não fornecerão esse valor de parâmetro. Por exemplo:

C#

public async Task<string> GetTotalLength(string param1, string param2)


{
return param1.Length + param2.Length;
}
Quando o cliente antigo tentar invocar esse método, ele receberá um erro como este:

Microsoft.AspNetCore.SignalR.HubException: Failed to invoke 'GetTotalLength'


due to an error on the server.

No servidor, você verá uma mensagem de log como esta:

System.IO.InvalidDataException: Invocation provides 1 argument(s) but target


expects 2.

O cliente antigo enviou apenas um parâmetro, mas a API de servidor mais recente exigia
dois parâmetros. Usar objetos personalizados como parâmetros oferece mais
flexibilidade. Vamos reprojetar a API original para usar um objeto personalizado:

C#

public class TotalLengthRequest


{
public string Param1 { get; set; }
}

public async Task GetTotalLength(TotalLengthRequest req)


{
return req.Param1.Length;
}

Agora, o cliente usa um objeto para chamar o método:

TypeScript

connection.invoke("GetTotalLength", { param1: "value1" });

Em vez de adicionar um parâmetro, adicione uma propriedade ao TotalLengthRequest


objeto:

C#

public class TotalLengthRequest


{
public string Param1 { get; set; }
public string Param2 { get; set; }
}
public async Task GetTotalLength(TotalLengthRequest req)
{
var length = req.Param1.Length;
if (req.Param2 != null)
{
length += req.Param2.Length;
}
return length;
}

Quando o cliente antigo envia um único parâmetro, a propriedade extra Param2 será
deixada null . Você pode detectar uma mensagem enviada por um cliente mais antigo
verificando e Param2 null aplicando um valor padrão. Um novo cliente pode enviar
ambos os parâmetros.

TypeScript

connection.invoke("GetTotalLength", { param1: "value1", param2: "value2" });

A mesma técnica funciona para métodos definidos no cliente. Você pode enviar um
objeto personalizado do lado do servidor:

C#

public async Task Broadcast(string message)


{
await Clients.All.SendAsync("ReceiveMessage", new
{
Message = message
});
}

No lado do cliente, você acessa a Message propriedade em vez de usar um parâmetro:

TypeScript

connection.on("ReceiveMessage", (req) => {


appendMessageToChatWindow(req.message);
});

Se você decidir adicionar posteriormente o remetente da mensagem ao conteúdo,


adicione uma propriedade ao objeto:

C#
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 estarão esperando o Sender valor, portanto, eles o
ignorarão. Um novo cliente pode aceitá-lo atualizando-o para ler a nova propriedade:

TypeScript

connection.on("ReceiveMessage", (req) => {


let message = req.message;
if (req.sender) {
message = req.sender + ": " + message;
}
appendMessageToChatWindow(message);
});

Nesse caso, o novo cliente também é tolerante a um servidor antigo que não fornece o
Sender valor. Como o servidor antigo não fornecerá o Sender valor, o cliente verifica se
ele existe antes de acessá-lo.
Usar filtros de hub no ASP.NET Core
SignalR
Artigo • 04/01/2023 • 4 minutos para o fim da leitura

Filtros de hub:

Estão disponíveis no ASP.NET Core 5.0 ou posterior.


Permitir que a lógica seja executada antes e depois que os métodos de hub são
invocados pelos clientes.

Este artigo fornece diretrizes para escrever e usar filtros de hub.

Configurar filtros de hub


Os filtros de hub podem ser aplicados globalmente ou por tipo de hub. A ordem na
qual os filtros são adicionados é a ordem na qual os filtros são executados. Os filtros de
hub global são executados antes dos filtros do hub local.

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddSignalR(options =>
{
// Global filters will run first
options.AddFilter<CustomFilter>();
}).AddHubOptions<ChatHub>(options =>
{
// Local filters will run second
options.AddFilter<CustomFilter2>();
});
}

Um filtro de hub pode ser adicionado de uma das seguintes maneiras:

Adicione um filtro por tipo concreto:

C#

hubOptions.AddFilter<TFilter>();

Isso será resolvido com a injeção de dependência (DI) ou o tipo ativado.

Adicione um filtro por tipo de runtime:


C#

hubOptions.AddFilter(typeof(TFilter));

Isso será resolvido por meio de DI ou tipo ativado.

Adicione um filtro por instância:

C#

hubOptions.AddFilter(new MyFilter());

Essa instância será usada como um singleton. Todas as invocações de método de


hub usarão a mesma instância.

Os filtros de hub são criados e descartados por invocação de hub. Se você quiser
armazenar o estado global no filtro ou nenhum estado, adicione o tipo de filtro do hub
ao DI como um singleton para obter melhor desempenho. Como alternativa, adicione o
filtro como uma instância, se puder.

Criar filtros de hub


Crie um filtro declarando uma classe que herda IHubFilter e adicione o
InvokeMethodAsync método. Há também OnConnectedAsync e OnDisconnectedAsync isso
pode ser implementado opcionalmente para encapsular os métodos e
OnDisconnectedAsync hub OnConnectedAsync , respectivamente.

C#

public class CustomFilter : IHubFilter


{
public async ValueTask<object> InvokeMethodAsync(
HubInvocationContext invocationContext, Func<HubInvocationContext,
ValueTask<object>> next)
{
Console.WriteLine($"Calling hub method
'{invocationContext.HubMethodName}'");
try
{
return await next(invocationContext);
}
catch (Exception ex)
{
Console.WriteLine($"Exception calling
'{invocationContext.HubMethodName}': {ex}");
throw;
}
}

// Optional method
public Task OnConnectedAsync(HubLifetimeContext context,
Func<HubLifetimeContext, Task> next)
{
return next(context);
}

// Optional method
public Task OnDisconnectedAsync(
HubLifetimeContext context, Exception exception,
Func<HubLifetimeContext, Exception, Task> next)
{
return next(context, exception);
}
}

Os filtros são muito semelhantes ao middleware. O next método invoca o próximo


filtro. O filtro final invocará o método hub. Os filtros também podem armazenar o
resultado da espera next e da lógica de execução após o método do hub ter sido
chamado antes de retornar o resultado. next

Para ignorar uma invocação de método hub em um filtro, gere uma exceção de tipo
HubException em vez de chamar next . O cliente receberá um erro se estivesse
esperando um resultado.

Usar filtros de hub


Ao escrever a lógica de filtro, tente torná-la genérica usando atributos em métodos de
hub em vez de verificar nomes de método de hub.

Considere um filtro que verificará um argumento de método hub para frases proibidas e
substituirá as frases encontradas por *** . Para este exemplo, suponha que uma
LanguageFilterAttribute classe seja definida. A classe tem uma propriedade nomeada
FilterArgument que pode ser definida ao usar o atributo.

1. Coloque o atributo no método hub que tem um argumento de cadeia de


caracteres a ser limpo:

C#

public class ChatHub


{
[LanguageFilter(filterArgument = 0)]
public async Task SendMessage(string message, string username)
{
await Clients.All.SendAsync("SendMessage", $"{username} says:
{message}");
}
}

2. Defina um filtro de hub para verificar o atributo e substituir frases proibidas em um


argumento de método hub por *** :

C#

public class LanguageFilter : IHubFilter


{
// populated from a file or inline
private List<string> bannedPhrases = new List<string> { "async
void", ".Result" };

public async ValueTask<object>


InvokeMethodAsync(HubInvocationContext invocationContext,
Func<HubInvocationContext, ValueTask<object>> next)
{
var languageFilter =
(LanguageFilterAttribute)Attribute.GetCustomAttribute(
invocationContext.HubMethod,
typeof(LanguageFilterAttribute));
if (languageFilter != null &&
invocationContext.HubMethodArguments.Count >
languageFilter.FilterArgument &&

invocationContext.HubMethodArguments[languageFilter.FilterArgument] is
string str)
{
foreach (var bannedPhrase in bannedPhrases)
{
str = str.Replace(bannedPhrase, "***");
}

var arguments =
invocationContext.HubMethodArguments.ToArray();
arguments[languageFilter.FilterArgument] = str;
invocationContext = new
HubInvocationContext(invocationContext.Context,
invocationContext.ServiceProvider,
invocationContext.Hub,
invocationContext.HubMethod,
arguments);
}

return await next(invocationContext);


}
}
3. Registre o filtro de hub no Startup.ConfigureServices método. Para evitar a
reinicialização da lista de frases proibidas para cada invocação, o filtro do hub é
registrado como um singleton:

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddSignalR(hubOptions =>
{
hubOptions.AddFilter<LanguageFilter>();
});

services.AddSingleton<LanguageFilter>();
}

O objeto HubInvocationContext
Contém HubInvocationContext informações para a invocação do método do hub atual.

Propriedade Descrição Type

Context Contém HubCallerContext informações sobre a HubCallerContext


conexão.

Hub A instância do Hub que está sendo usado para Hub


essa invocação de método de hub.

HubMethodName O nome do método hub que está sendo string


invocado.

HubMethodArguments A lista de argumentos que estão sendo passados IReadOnlyList<string>


para o método hub.

ServiceProvider O provedor de serviços com escopo para essa IServiceProvider


invocação de método de hub.

HubMethod As informações do método hub. MethodInfo

O objeto HubLifetimeContext
Contém HubLifetimeContext informações para os métodos e OnDisconnectedAsync os
métodos de OnConnectedAsync hub.

Propriedade Descrição Type


Propriedade Descrição Type

Context Contém HubCallerContext informações sobre a conexão. HubCallerContext

Hub A instância do Hub que está sendo usado para essa Hub
invocação de método de hub.

ServiceProvider O provedor de serviços com escopo para essa invocação IServiceProvider


de método de hub.

Autorização e filtros
Autorizar atributos em métodos de hub executados antes dos filtros do hub.
SignalR ASP.NET Core clientes
Artigo • 04/01/2023 • 2 minutos para o fim da leitura

Controle de versão, suporte e compatibilidade


Os SignalR clientes são enviados juntamente com os componentes do servidor e têm a
versão para corresponder. Qualquer cliente com suporte pode se conectar com
segurança a qualquer servidor com suporte, e quaisquer problemas de compatibilidade
seriam considerados bugs a serem corrigidos. SignalR os clientes têm suporte no
mesmo ciclo de vida de suporte que o restante do .NET Core. Consulte a Política de
Suporte do .NET Core para obter detalhes.

Muitos recursos exigem um cliente e um servidor compatíveis. Veja abaixo uma tabela
mostrando as versões mínimas para vários recursos.

As versões 1.x do SignalR mapa para as versões do .NET Core 2.1 e 2.2 e têm o mesmo
tempo de vida. Para a versão 3.x e superior, a SignalR versão corresponde exatamente
ao restante do .NET e tem o mesmo ciclo de vida de suporte.

Versão do Versão do .NET Nível de suporte Fim do suporte


SignalR Core

1.0.x 2.1.x Suporte de longo prazo 21 de agosto de


2021

1.1.x 2.2.x Fim da vida útil 23 de dezembro


de 2019

3.x ou mesmo que a SignalR Consulte a política de suporte do


superior versão .NET Core

NOTA: No ASP.NET Core 3.0, o cliente JavaScript foi movido para o @microsoft/signalr
pacote npm.

Distribuição de recursos
A tabela abaixo mostra os recursos e o suporte para os clientes que oferecem suporte
em tempo real. Para cada recurso, a versão mínima que dá suporte a esse recurso está
listada. Se nenhuma versão estiver listada, o recurso não terá suporte.

Recurso Servidor Cliente Cliente Cliente


.NET JavaScript Java
Recurso Servidor Cliente Cliente Cliente
.NET JavaScript Java

Suporte ao Serviço do Azure SignalR 2.1.0 1.0.0 1.0.0 1.0.0

Streaming de servidor para cliente 2.1.0 1.0.0 1.0.0 1.0.0

Streaming de cliente para servidor 3.0.0 3.0.0 3.0.0 3.0.0

Reconexão automática (.NET, 3.0.0 3.0.0 3.0.0 ❌


JavaScript)

Transporte WebSockets 2.1.0 1.0.0 1.0.0 1.0.0

Transporte de eventos Server-Sent 2.1.0 1.0.0 1.0.0 ❌

Transporte de sondagem longa 2.1.0 1.0.0 1.0.0 3.0.0

JSProtocolo ON Hub 2.1.0 1.0.0 1.0.0 1.0.0

Protocolo de Hub do MessagePack 2.1.0 1.0.0 1.0.0 5.0.0

Resultados do cliente 7.0.0 7.0.0 7.0.0 ❌

O suporte para habilitar recursos adicionais do cliente é acompanhado em nosso


rastreador de problemas .

Recursos adicionais
Introdução ao SignalR ASP.NET Core
Plataformas com suporte
Hubs
Cliente JavaScript
SignalR ASP.NET Core cliente .NET
Artigo • 04/01/2023 • 8 minutos para o fim da leitura

A ASP.NET Core SignalR biblioteca de clientes do .NET permite que você se comunique
com SignalR hubs de aplicativos .NET.

Exibir ou baixar código de exemplo (como baixar)

O exemplo de código neste artigo é um aplicativo WPF que usa o ASP.NET Core SignalR
cliente .NET.

Instalar o SignalR pacote do cliente .NET


O Microsoft.AspNetCore.SignalR. O pacote cliente é necessário para que os clientes
do .NET se conectem aos SignalR hubs.

Visual Studio

Para instalar a biblioteca de clientes, execute o seguinte comando na janela console


do Gerenciador de Pacotes :

PowerShell

Install-Package Microsoft.AspNetCore.SignalR.Client

Conectar-se a um hub
Para estabelecer uma conexão, crie um HubConnectionBuilder e chame Build . A URL do
hub, o protocolo, o tipo de transporte, o nível do log, os cabeçalhos e outras opções
podem ser configurados durante a criação de uma conexão. Configure todas as opções
necessárias inserindo qualquer um dos HubConnectionBuilder métodos em Build . Inicie
a conexão com StartAsync .

C#

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();

connection = new HubConnectionBuilder()


.WithUrl("http://localhost:53353/ChatHub")
.Build();

connection.Closed += async (error) =>


{
await Task.Delay(new Random().Next(0,5) * 1000);
await connection.StartAsync();
};
}

private async void connectButton_Click(object sender,


RoutedEventArgs e)
{
connection.On<string, string>("ReceiveMessage", (user, message)
=>
{
this.Dispatcher.Invoke(() =>
{
var newMessage = $"{user}: {message}";
messagesList.Items.Add(newMessage);
});
});

try
{
await connection.StartAsync();
messagesList.Items.Add("Connection started");
connectButton.IsEnabled = false;
sendButton.IsEnabled = true;
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
}

private async void sendButton_Click(object sender, RoutedEventArgs


e)
{
try
{
await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
}
}
}

Manipular conexão perdida

Reconectar automaticamente
O HubConnection método pode ser configurado para se reconectar automaticamente
usando o WithAutomaticReconnect HubConnectionBuildermétodo . Ele não se
reconectará automaticamente por padrão.

C#

HubConnection connection= new HubConnectionBuilder()


.WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
.WithAutomaticReconnect()
.Build();

Sem parâmetros, WithAutomaticReconnect() configura o cliente para aguardar 0, 2, 10 e


30 segundos, respectivamente, antes de tentar cada tentativa de reconexão, parando
após quatro tentativas com falha.

Antes de iniciar qualquer tentativa de reconexão, a HubConnection transição será para o


HubConnectionState.Reconnecting estado e disparará o Reconnecting evento. Isso

oferece uma oportunidade para avisar os usuários de que a conexão foi perdida e
desabilitar elementos da interface do usuário. Aplicativos não interativos podem
começar a enfileirar ou soltar mensagens.

C#

connection.Reconnecting += error =>


{
Debug.Assert(connection.State == HubConnectionState.Reconnecting);

// Notify users the connection was lost and the client is reconnecting.
// Start queuing or dropping messages.

return Task.CompletedTask;
};
Se o cliente se reconectar com êxito em suas quatro primeiras tentativas, ele fará a
HubConnection transição de volta para o Connected estado e disparará o Reconnected
evento. Isso oferece uma oportunidade para informar aos usuários que a conexão foi
restabelecida e desativar as mensagens enfileiradas.

Como a conexão parece totalmente nova para o servidor, uma nova ConnectionId será
fornecida aos Reconnected manipuladores de eventos.

2 Aviso

O Reconnected parâmetro do manipulador de connectionId eventos será nulo se o


HubConnection foi configurado para ignorar a negociação.

C#

connection.Reconnected += connectionId =>


{
Debug.Assert(connection.State == HubConnectionState.Connected);

// Notify users the connection was reestablished.


// Start dequeuing messages queued while reconnecting if any.

return Task.CompletedTask;
};

WithAutomaticReconnect() não configurará as HubConnection falhas de início iniciais de

repetição, portanto, as falhas de início precisam ser tratadas manualmente:

C#

public static async Task<bool> ConnectWithRetryAsync(HubConnection


connection, CancellationToken token)
{
// Keep trying to until we can start or the token is canceled.
while (true)
{
try
{
await connection.StartAsync(token);
Debug.Assert(connection.State == HubConnectionState.Connected);
return true;
}
catch when (token.IsCancellationRequested)
{
return false;
}
catch
{
// Failed to connect, trying again in 5000 ms.
Debug.Assert(connection.State ==
HubConnectionState.Disconnected);
await Task.Delay(5000);
}
}
}

Se o cliente não se reconectar com êxito em suas quatro primeiras tentativas, ele fará a
HubConnection transição para o Disconnected estado e disparará o Closed evento. Isso

oferece uma oportunidade para tentar reiniciar a conexão manualmente ou informar


aos usuários que a conexão foi perdida permanentemente.

C#

connection.Closed += error =>


{
Debug.Assert(connection.State == HubConnectionState.Disconnected);

// Notify users the connection has been closed or manually try to


restart the connection.

return Task.CompletedTask;
};

Para configurar um número personalizado de tentativas de reconexão antes de


desconectar ou alterar o tempo de reconexão, WithAutomaticReconnect aceita uma
matriz de números que representam o atraso em milissegundos para aguardar antes de
iniciar cada tentativa de reconexão.

C#

HubConnection connection= new HubConnectionBuilder()


.WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
.WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.FromSeconds(10) })
.Build();

// .WithAutomaticReconnect(new[] { TimeSpan.Zero,
TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30)
}) yields the default behavior.

O exemplo anterior configura a origem da HubConnection tentativa de reconexão


imediatamente após a perda da conexão. Isso também é verdadeiro para a configuração
padrão.
Se a primeira tentativa de reconexão falhar, a segunda tentativa de reconexão também
será iniciada imediatamente em vez de aguardar 2 segundos como faria na
configuração padrão.

Se a segunda tentativa de reconexão falhar, a terceira tentativa de reconexão será


iniciada em 10 segundos, o que é novamente como a configuração padrão.

O comportamento personalizado diverge novamente do comportamento padrão


parando após a terceira falha de tentativa de reconexão. Na configuração padrão,
haveria mais uma tentativa de reconexão em mais 30 segundos.

Se você quiser ainda mais controle sobre o tempo e o número de tentativas automáticas
de reconexão, WithAutomaticReconnect aceita um objeto que implementa a
IRetryPolicy interface, que tem um único método chamado NextRetryDelay .

NextRetryDelay usa um único argumento com o tipo RetryContext . O RetryContext tem


três propriedades: PreviousRetryCount , ElapsedTime e RetryReason , que são um long ,
um TimeSpan e um Exception , respectivamente. Antes da primeira tentativa de
reconexão, ambos PreviousRetryCount e ElapsedTime serão zero, e a RetryReason
exceção será a exceção que fez com que a conexão fosse perdida. Após cada tentativa
de repetição com falha, PreviousRetryCount será incrementada por uma, ElapsedTime
será atualizada para refletir o tempo gasto para se reconectar até agora e a RetryReason
exceção que causou a falha da última tentativa de reconexão.

NextRetryDelay deve retornar um TimeSpan que represente o tempo de espera antes da

próxima tentativa de reconexão ou null se deve parar de HubConnection se reconectar.

C#

public class RandomRetryPolicy : IRetryPolicy


{
private readonly Random _random = new Random();

public TimeSpan? NextRetryDelay(RetryContext retryContext)


{
// If we've been reconnecting for less than 60 seconds so far,
// wait between 0 and 10 seconds before the next reconnect attempt.
if (retryContext.ElapsedTime < TimeSpan.FromSeconds(60))
{
return TimeSpan.FromSeconds(_random.NextDouble() * 10);
}
else
{
// If we've been reconnecting for more than 60 seconds so far,
stop reconnecting.
return null;
}
}
}

C#

HubConnection connection = new HubConnectionBuilder()


.WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
.WithAutomaticReconnect(new RandomRetryPolicy())
.Build();

Como alternativa, você pode escrever um código que reconectará seu cliente
manualmente, conforme demonstrado na reconexão manual.

Reconectar manualmente
Use o Closed evento para responder a uma conexão perdida. Por exemplo, talvez você
queira automatizar a reconexão.

O Closed evento requer um delegado que retorna um Task código assíncrono para ser
executado sem usar async void . Para satisfazer a assinatura de delegado em um Closed
manipulador de eventos que é executado de forma síncrona, retorne
Task.CompletedTask :

C#

connection.Closed += (error) => {


// Do your close logic.
return Task.CompletedTask;
};

O principal motivo para o suporte assíncrono é para que você possa 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 para evitar sobrecarga do servidor, conforme mostrado no exemplo a seguir:

C#

connection.Closed += async (error) =>


{
await Task.Delay(new Random().Next(0,5) * 1000);
await connection.StartAsync();
};
Métodos de hub de chamadas do cliente
InvokeAsync chama métodos no hub. Passe o nome do método hub e todos os

argumentos definidos no método hub para InvokeAsync . SignalR é assíncrono, portanto,


use async e await ao fazer as chamadas.

C#

await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);

O InvokeAsync método retorna um Task que é concluído quando o método de servidor


retorna. O valor retornado, se houver, é fornecido como o resultado do Task . Quaisquer
exceções geradas pelo método no servidor produzem uma falha Task . Use await a
sintaxe para aguardar a conclusão do método de servidor e try...catch a sintaxe para
lidar com erros.

O SendAsync método retorna um Task que é concluído quando a mensagem é enviada


ao servidor. Nenhum valor retornado é fornecido, pois isso Task não aguarda até que o
método do servidor seja concluído. Todas as exceções geradas no cliente ao enviar a
mensagem produzem uma falha Task . Use await e try...catch sintaxe para lidar com
erros de envio.

7 Observação

Os métodos de hub de chamada de um cliente só têm suporte ao usar o Serviço do


Azure SignalR no modo Padrão . Para obter mais informações, consulte Perguntas
frequentes (repositório GitHub do azure-signalr) .

Chamar métodos de cliente do hub


Defina métodos que o hub chama usando connection.On após a criação, mas antes de
iniciar a conexão.

C#

connection.On<string, string>("ReceiveMessage", (user, message) =>


{
this.Dispatcher.Invoke(() =>
{
var newMessage = $"{user}: {message}";
messagesList.Items.Add(newMessage);
});
});

O código anterior em connection.On execuções quando o código do lado do servidor o


chama usando o SendAsync método.

C#

public async Task SendMessage(string user, string message)


{
await Clients.All.SendAsync("ReceiveMessage", user,message);
}

7 Observação

Embora o lado do hub da conexão dê suporte a mensagens fortemente tipada, o


cliente deve se registrar usando o método HubConnection.On genérico com o
nome do método. Para obter um exemplo, consulte o host ASP.NET Core SignalR
em serviços em segundo plano.

Registro em log e tratamento de erros


Manipular erros com uma instrução try-catch. Inspecione o Exception objeto para
determinar a ação adequada a ser tomada após a ocorrência de um erro.

C#

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
Documentação sem servidor do Serviço do Azure SignalR
Microsoft.AspNetCore.SignalR.Client
Namespace
Referência  

Contém tipos que são usados para se comunicar com um servidor SignalR.

Neste artigo
Classes
Interfaces
Enumerações
Comentários

Classes
HubConnection Uma conexão usada para invocar métodos de hub em um
Servidor SignalR.

HubConnectionBuilder Um construtor para configurar HubConnection instâncias.

HubConnectionBuilder Métodos de extensão para IHubConnectionBuilder.


Extensions

HubConnectionBuilderHttp Métodos de extensão para IHubConnectionBuilder.


Extensions

HubConnectionExtensions Métodos de extensão para HubConnectionExtensions.

RetryContext O contexto passado para NextRetryDelay(RetryContext) ajudar a


política a determinar quanto tempo aguardar antes da próxima
repetição e se deve haver outra repetição.

Interfaces
IHubConnectionBuilder Uma abstração de construtor para configurar instâncias de
HubConnection.

IRetryPolicy Uma abstração que controla quando o cliente tenta se


reconectar e quantas vezes ele faz isso.

Enumerações
HubConnectionState Descreve o estado atual do HubConnection servidor.

Comentários
Para obter mais informações sobre o cliente SignalR, consulte ASP.NET Core Cliente
.NET do SignalR.
SignalR ASP.NET Core cliente Java
Artigo • 14/01/2023 • 4 minutos para o fim da leitura

Por Mikael Mengistu

O cliente Java permite conectar-se a um servidor ASP.NET Core SignalR do código Java,
incluindo aplicativos Android. Assim como o cliente JavaScript e o cliente .NET, o cliente
Java permite que você receba e envie mensagens para um hub em tempo real. O cliente
Java está disponível no ASP.NET Core 2.2 e posterior.

O aplicativo de console Java de exemplo referenciado neste artigo usa o SignalR cliente
Java.

Exibir ou baixar código de exemplo (como baixar)

Instalar o SignalR pacote do cliente Java


O arquivo JAR signalr-7.0.0 permite que os clientes se conectem aos SignalR hubs. Para
localizar o número de versão mais recente do arquivo JAR, consulte os resultados da
pesquisa do Maven .

Se estiver usando o Gradle, adicione a seguinte linha à dependencies seção do arquivo


build.gradle :

Gradle

implementation 'com.microsoft.signalr:signalr:7.0.0'

Se estiver usando o Maven, adicione as seguintes linhas dentro do <dependencies>


elemento do pom.xml arquivo:

XML

<dependency>
<groupId>com.microsoft.signalr</groupId>
<artifactId>signalr</artifactId>
<version>1.0.0</version>
</dependency>

Conectar-se a um hub
Para estabelecer um HubConnection , o HubConnectionBuilder deve ser usado. A URL do
hub e o nível de log podem ser configurados durante a criação de uma conexão.
Configure as opções necessárias chamando qualquer um dos HubConnectionBuilder
métodos antes build de . Inicie a conexão com start .

Java

HubConnection hubConnection = HubConnectionBuilder.create(input)


.build();

Métodos de hub de chamada do cliente


Uma chamada para send invocar um método de hub. Passe o nome do método hub e
todos os argumentos definidos no método hub para send .

Java

hubConnection.send("Send", input);

7 Observação

Os métodos de hub de chamada de um cliente só têm suporte ao usar o Serviço do


Azure SignalR no modo Padrão . Para obter mais informações, consulte Perguntas
frequentes (repositório GitHub azure-signalr) .

Chamar métodos de cliente do hub


Use hubConnection.on para definir métodos no cliente que o hub pode chamar. Defina
os métodos após a criação, mas antes de iniciar a conexão.

Java

hubConnection.on("Send", (message) -> {


System.out.println("New Message: " + message);
}, String.class);

Adicionar registro em log


O SignalR cliente Java usa a biblioteca SLF4J para registro em log. É uma API de log
de alto nível que permite que os usuários da biblioteca escolham sua própria
implementação de log específica trazendo uma dependência de log específica. O
snippet de código a seguir mostra como usar java.util.logging com o SignalR cliente
Java.

Gradle

implementation 'org.slf4j:slf4j-jdk14:1.7.25'

Se você não configurar o registro em log em suas dependências, o SLF4J carregará um


agente sem operação padrão com a seguinte mensagem de aviso:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".


SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further
details.

Isso pode ser ignorado com segurança.

Notas de desenvolvimento do Android


Em relação à compatibilidade do SDK do Android para os recursos do SignalR cliente,
considere os seguintes itens ao especificar a versão de destino do SDK do Android:

O SignalR Cliente Java será executado no Nível 16 da API do Android e posterior.


A conexão por meio do Serviço do Azure SignalR exigirá o Nível 20 da API do
Android e posterior, pois o Serviço do Azure SignalR requer TLS 1.2 e não dá
suporte a pacotes de criptografia baseados em SHA-1. O Android adicionou
suporte para pacotes de criptografia SHA-256 (e superiores) no Nível 20 da API.

Configurar a autenticação de token de


portador
SignalR No cliente Java, você pode configurar um token de portador a ser usado para
autenticação fornecendo uma "fábrica de tokens de acesso" para o
HttpHubConnectionBuilder. Use withAccessTokenFactory para fornecer uma cadeia de
caracteres única<> RxJava . Com uma chamada para Single.defer , você pode
escrever lógica para produzir tokens de acesso para seu cliente.
Java

HubConnection hubConnection = HubConnectionBuilder.create("YOUR HUB URL


HERE")
.withAccessTokenProvider(Single.defer(() -> {
// Your logic here.
return Single.just("An Access Token");
})).build();

Passando informações de classe em Java


Ao chamar os on métodos , invoke ou stream de HubConnection no cliente Java, os
usuários devem passar um Type objeto em vez de um Class<?> objeto para descrever
qualquer genérico Object passado para o método . Um Type pode ser adquirido
usando a classe fornecida TypeReference . Por exemplo, usando uma classe genérica
personalizada chamada Foo<T> , o código a seguir obtém o Type :

Java

Type fooType = new TypeReference<Foo<String>>() { }).getType();

Para não genéricos, como primitivos ou outros tipos não parametrizados, como String ,
você pode simplesmente usar o interno .class .

Ao chamar um desses métodos com um ou mais tipos de objeto, use a sintaxe generics
ao invocar o método . Por exemplo, ao registrar um on manipulador para um método
chamado func , que usa como argumentos um String e um Foo<String> objeto , use o
seguinte código para definir uma ação para imprimir os argumentos:

Java

hubConnection.<String, Foo<String>>on("func", (param1, param2) ->{


System.out.println(param1);
System.out.println(param2);
}, String.class, fooType);

Essa convenção é necessária porque não é possível recuperar informações completas


sobre tipos complexos com o Object.getClass método devido à eliminação de tipo em
Java. Por exemplo, chamar getClass em um ArrayList<String> não retornaria
Class<ArrayList<String>> , mas sim Class<ArrayList> , que não fornece ao
desserializador informações suficientes para desserializar corretamente uma mensagem
de entrada. O mesmo vale para objetos personalizados.
Limitações conhecidas
Não há suporte para o fallback de transporte e o transporte de Eventos Enviados
pelo Servidor.

Recursos adicionais
Referência da API Java
Usar hubs no ASP.NET Core SignalR
SignalR ASP.NET Core cliente JavaScript
Publicar um aplicativo de ASP.NET Core SignalR para Serviço de Aplicativo do
Azure
Documentação sem servidor do Serviço do Azure SignalR
com.microsoft.signalr
Referência
Pacote: com.microsoft.signalr
Artefato Maven: com.microsoft.signalr:signalr:5.0.10

Esse pacote contém as classes para o cliente Java do SignalR.

Neste artigo
Classes
Interfaces
Enumerações

Classes
CancelInvocationMessage Esse pacote contém as classes para o cliente Java do SignalR.

CloseMessage Esse pacote contém as classes para o cliente Java do SignalR.

CompletionMessage Esse pacote contém as classes para o cliente Java do SignalR.

HttpHubConnectionBuilder Um construtor para configurar HubConnection instâncias.

HubConnection Uma conexão usada para invocar métodos de hub em um


Servidor SignalR.

HubConnectionBuilder Um construtor para configurar HubConnection instâncias.

HubException Uma exceção gerada quando o servidor falha ao invocar um


método Hub.

HubMessage Uma classe base para mensagens de hub.

InvocationBindingFailure Esse pacote contém as classes para o cliente Java do SignalR.


Message

InvocationMessage Esse pacote contém as classes para o cliente Java do SignalR.

PingMessage Esse pacote contém as classes para o cliente Java do SignalR.

StreamBindingFailureMessage Esse pacote contém as classes para o cliente Java do SignalR.

StreamInvocationMessage Esse pacote contém as classes para o cliente Java do SignalR.

StreamItem Esse pacote contém as classes para o cliente Java do SignalR.

Subscription Representa o registro de um manipulador para um método de


cliente.
TypeReference<T> Um utilitário para obter um Tipo Java de uma Classe genérica
literal.

UserAgentHelper Esse pacote contém as classes para o cliente Java do SignalR.

Interfaces
Action Um retorno de chamada que não usa parâmetros.

Action1<T1> Um retorno de chamada que usa um parâmetro.

Action2<T1,T2> Um retorno de chamada que usa dois parâmetros.

Action3<T1,T2,T3> Um retorno de chamada que usa três parâmetros.

Action4<T1,T2,T3,T4> Um retorno de chamada que usa quatro parâmetros.

Action5<T1,T2,T3,T4,T5> Um retorno de chamada que usa cinco parâmetros.

Action6<T1,T2,T3,T4,T5,T6> Um retorno de chamada que usa seis parâmetros.

Action7<T1,T2,T3,T4,T5,T6,T7> Um retorno de chamada que usa sete parâmetros.

Action8<T1,T2,T3,T4,T5,T6,T7,T8> Um retorno de chamada que usa oito parâmetros.

HubProtocol Uma abstração de protocolo para se comunicar com hubs do


SignalR.

InvocationBinder Uma abstração para transmitir informações sobre assinaturas


de método.

OnClosedCallback Um retorno de chamada para criar e registrar em um método


HubConnections OnClosed.

Enumerações
HubConnectionState Indica o estado do HubConnection.

HubMessageType Esse pacote contém as classes para o cliente Java do SignalR.

TransportEnum Usado para especificar o transporte que o cliente usará.


SignalR ASP.NET Core cliente JavaScript
Artigo • 04/01/2023 • 22 minutos para o fim da leitura

Por Rachel Appel

A biblioteca de SignalR clientes ASP.NET Core JavaScript permite que os


desenvolvedores chamem o código do hub do lado SignalR do servidor.

Instalar o SignalR pacote do cliente


A SignalR biblioteca de clientes JavaScript é entregue como um pacote npm . As
seções a seguir descrevem diferentes maneiras de instalar a biblioteca de clientes.

Instalar com o npm

Visual Studio

Execute os seguintes comandos no Console do Gerenciador de Pacotes:

Bash

npm init -y
npm install @microsoft/signalr

O npm instala o conteúdo do pacote na pasta


node_modules\@microsoft\signalr\dist\browser . Crie a pasta wwwroot/lib/signalr . Copie
o signalr.js arquivo para a pasta wwwroot/lib/signalr .

Faça referência ao SignalR cliente JavaScript no <script> elemento. Por exemplo:

HTML

<script src="~/lib/signalr/signalr.js"></script>

Usar uma CDN (Rede de Distribuição de Conteúdo)


Para usar a biblioteca de clientes sem o pré-requisito npm, faça referência a uma cópia
hospedada por CDN da biblioteca de clientes. Por exemplo:

HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-
signalr/6.0.1/signalr.js"></script>

A biblioteca de clientes está disponível nos seguintes CDNs:

cdnjs
jsDelivr
unpkg

Instalar com o LibMan


O LibMan pode ser usado para instalar arquivos de biblioteca de clientes específicos da
biblioteca de clientes hospedada pela CDN. Por exemplo, adicione apenas o arquivo
JavaScript minificado ao projeto. Para obter detalhes sobre essa abordagem, consulte
Adicionar a SignalR biblioteca de clientes.

Conectar-se a um hub
O código a seguir cria e inicia uma conexão. O nome do hub não diferencia maiúsculas
de minúsculas:

JavaScript

const connection = new signalR.HubConnectionBuilder()


.withUrl("/chathub")
.configureLogging(signalR.LogLevel.Information)
.build();

async function start() {


try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};

connection.onclose(async () => {
await start();
});

// Start the connection.


start();
CORS (conexões entre origens)
Normalmente, os navegadores carregam conexões do mesmo domínio que a página
solicitada. No entanto, há ocasiões em que uma conexão com outro domínio é
necessária.

Ao fazer solicitações entre domínios, o código do cliente deve usar uma URL absoluta
em vez de uma URL relativa. Para solicitações entre domínios, altere
.withUrl("/chathub") para .withUrl("https://{App domain name}/chathub") .

Para impedir que um site mal-intencionado leia dados confidenciais de outro site, as
conexões entre origens são desabilitadas por padrão. Para permitir uma solicitação
entre origens, habilite CORS:

C#

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("https://example.com")
.AllowAnyHeader()
.WithMethods("GET", "POST")
.AllowCredentials();
});
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();
// UseCors must be called before MapHub.
app.UseCors();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();

UseCors deve ser chamado antes de chamar MapHub.

Métodos de hub de chamadas do cliente


Os clientes JavaScript chamam métodos públicos em hubs por meio do método de
invocação do HubConnection. O invoke método aceita:

O nome do método hub.


Todos os argumentos definidos no método hub.

No código realçado a seguir, o nome do método no hub é SendMessage . O segundo e o


terceiro argumentos passados para invoke mapear para os argumentos e message o
método do user hub:

JavaScript

try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}

Os métodos de hub de chamada de um cliente só têm suporte ao usar o Serviço do


Azure SignalR no modo Padrão . Para obter mais informações, consulte Perguntas
frequentes (repositório GitHub do azure-signalr) .

O invoke método retorna um JavaScript Promise . Ele Promise é resolvido com o valor
retornado (se houver) quando o método no servidor é retornado. Se o método no
servidor gerar um erro, ele Promise será rejeitado com a mensagem de erro. Use async
e await ou os métodos e catch os Promise 's then para lidar com esses casos.

Os clientes JavaScript também podem chamar métodos públicos em hubs por meio do
método de envio do HubConnection . Ao contrário do invoke método, o send método
não aguarda uma resposta do servidor. O send método retorna um JavaScript Promise .
A Promise solução é quando a mensagem é enviada ao servidor. Se houver um erro ao
enviar a mensagem, a Promise mensagem de erro será rejeitada. Use async e await ou
os métodos e catch os Promise 's then para lidar com esses casos.

O uso send não aguarda até que o servidor tenha recebido a mensagem.
Consequentemente, não é possível retornar dados ou erros do servidor.

Chamar métodos de cliente do hub


Para receber mensagens do hub, defina um método usando o método on do
HubConnection .

O nome do método cliente JavaScript.


Argumentos que o hub passa para o método.

No exemplo a seguir, o nome do método é ReceiveMessage . Os nomes dos argumentos


são user : message

JavaScript

connection.on("ReceiveMessage", (user, message) => {


const li = document.createElement("li");
li.textContent = `${user}: ${message}`;
document.getElementById("messageList").appendChild(li);
});

O código anterior em connection.on execuções quando o código do lado do servidor o


chama usando o SendAsync método:

C#

using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;

public class ChatHub : Hub


{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}

SignalR determina em qual método cliente chamar, correspondendo ao nome do


método e aos argumentos definidos em SendAsync e connection.on .
Uma prática recomendada é chamar o método de início no HubConnection seguinte on .
Isso garante que os manipuladores sejam registrados antes que qualquer mensagem
seja recebida.

Registro em log e tratamento de erros


Use console.error para gerar erros no console do navegador quando o cliente não
puder se conectar ou enviar uma mensagem:

JavaScript

try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}

Configure o rastreamento de log do lado do cliente passando um agente e o tipo de


evento para fazer logon quando a conexão for feita. As mensagens são registradas com
o nível de log especificado e superior. Os níveis de log disponíveis são os seguintes:

signalR.LogLevel.Error : mensagens de erro. Registra mensagens Error somente

em log.
signalR.LogLevel.Warning : mensagens de aviso sobre possíveis erros. Logs

Warning e Error mensagens.

signalR.LogLevel.Information : mensagens de status sem erros. Logs


Information Warning e Error mensagens.

signalR.LogLevel.Trace : rastrear mensagens. Registra tudo, incluindo dados


transportados entre o hub e o cliente.

Use o método configureLogging no HubConnectionBuilder para configurar o nível de


log. As mensagens são registradas no console do navegador:

JavaScript

const connection = new signalR.HubConnectionBuilder()


.withUrl("/chathub")
.configureLogging(signalR.LogLevel.Information)
.build();

Reconectar clientes
Reconectar automaticamente
O cliente JavaScript para SignalR o qual pode ser configurado para se reconectar
automaticamente usando o método WithAutomaticReconnect no
HubConnectionBuilder. Ele não se reconectará automaticamente por padrão.

JavaScript

const connection = new signalR.HubConnectionBuilder()


.withUrl("/chathub")
.withAutomaticReconnect()
.build();

Sem parâmetros, WithAutomaticReconnect configura o cliente para aguardar 0, 2, 10 e


30 segundos, respectivamente, antes de tentar cada tentativa de reconexão. Após
quatro tentativas com falha, ele para de tentar se reconectar.

Antes de iniciar qualquer tentativa de reconexão, o HubConnection :

Faz a transição para o HubConnectionState.Reconnecting estado e dispara seus


onreconnecting retornos de chamada.
Não faz a transição para o Disconnected estado e dispara seus onclose retornos
de chamada como um HubConnection sem reconexão automática configurado.

A abordagem de reconexão oferece uma oportunidade para:

Avisar os usuários de que a conexão foi perdida.


Desabilite os elementos da interface do usuário.

JavaScript

connection.onreconnecting(error => {
console.assert(connection.state ===
signalR.HubConnectionState.Reconnecting);

document.getElementById("messageInput").disabled = true;

const li = document.createElement("li");
li.textContent = `Connection lost due to error "${error}".
Reconnecting.`;
document.getElementById("messageList").appendChild(li);
});

Se o cliente se reconectar com êxito em suas quatro primeiras tentativas, ele fará a
HubConnection transição de volta para o Connected estado e disparará seus
onreconnected retornos de chamada. Isso oferece uma oportunidade para informar aos

usuários que a conexão foi restabelecida.

Como a conexão parece totalmente nova para o servidor, uma nova connectionId é
fornecida para o onreconnected retorno de chamada.

O onreconnected parâmetro do retorno de connectionId chamada será indefinido se ele


HubConnection estiver configurado para ignorar a negociação.

JavaScript

connection.onreconnected(connectionId => {
console.assert(connection.state ===
signalR.HubConnectionState.Connected);

document.getElementById("messageInput").disabled = false;

const li = document.createElement("li");
li.textContent = `Connection reestablished. Connected with connectionId
"${connectionId}".`;
document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect não configurará as HubConnection falhas de início iniciais para

tentar novamente, portanto, as falhas de início precisam ser tratadas manualmente:

JavaScript

async function start() {


try {
await connection.start();
console.assert(connection.state ===
signalR.HubConnectionState.Connected);
console.log("SignalR Connected.");
} catch (err) {
console.assert(connection.state ===
signalR.HubConnectionState.Disconnected);
console.log(err);
setTimeout(() => start(), 5000);
}
};

Se o cliente não se reconectar com êxito em suas quatro primeiras tentativas, a


HubConnection transição para o Disconnected estado e disparará seus retornos de

chamada de encerramento. Isso oferece uma oportunidade para informar os usuários:

A conexão foi perdida permanentemente.


Tente atualizar a página:
JavaScript

connection.onclose(error => {
console.assert(connection.state ===
signalR.HubConnectionState.Disconnected);

document.getElementById("messageInput").disabled = true;

const li = document.createElement("li");
li.textContent = `Connection closed due to error "${error}". Try
refreshing this page to restart the connection.`;
document.getElementById("messageList").appendChild(li);
});

Para configurar um número personalizado de tentativas de reconexão antes de


desconectar ou alterar o tempo de reconexão, withAutomaticReconnect aceita uma
matriz de números que representam o atraso em milissegundos para aguardar antes de
iniciar cada tentativa de reconexão.

JavaScript

const connection = new signalR.HubConnectionBuilder()


.withUrl("/chathub")
.withAutomaticReconnect([0, 0, 10000])
.build();

// .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default


behavior

O exemplo anterior configura a HubConnection tentativa de se reconectar


imediatamente após a perda da conexão. A configuração padrão também aguarda zero
segundos para tentar se reconectar.

Se a primeira tentativa de reconexão falhar, a segunda tentativa de reconexão também


será iniciada imediatamente em vez de aguardar 2 segundos usando a configuração
padrão.

Se a segunda tentativa de reconexão falhar, a terceira tentativa de reconexão será


iniciada em 10 segundos, o que é o mesmo que a configuração padrão.

O tempo de reconexão configurado difere do comportamento padrão, parando após a


terceira falha de tentativa de reconexão em vez de tentar mais uma tentativa de
reconexão em mais 30 segundos.

Para obter mais controle sobre o tempo e o número de tentativas de reconexão


automática, withAutomaticReconnect aceita um objeto que implementa a IRetryPolicy
interface, que tem um único método chamado nextRetryDelayInMilliseconds .

nextRetryDelayInMilliseconds usa um único argumento com o tipo RetryContext . O


RetryContext tem três propriedades: previousRetryCount , elapsedMilliseconds e

retryReason quais são um number , um number e um Error , respectivamente. Antes da


primeira tentativa de reconexão, ambos previousRetryCount serão elapsedMilliseconds
zero e o retryReason erro que causou a perda da conexão. Após cada tentativa de
repetição com falha, previousRetryCount será incrementada por um,
elapsedMilliseconds será atualizada para refletir a quantidade de tempo gasto se

reconectando até agora em milissegundos e o retryReason erro que causou a última


tentativa de reconexão falhar.

nextRetryDelayInMilliseconds deve retornar um número que represente o número de

milissegundos a aguardar antes da próxima tentativa de reconexão ou null se o


HubConnection deve parar de se reconectar.

JavaScript

const connection = new signalR.HubConnectionBuilder()


.withUrl("/chathub")
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: retryContext => {
if (retryContext.elapsedMilliseconds < 60000) {
// If we've been reconnecting for less than 60 seconds so
far,
// wait between 0 and 10 seconds before the next reconnect
attempt.
return Math.random() * 10000;
} else {
// If we've been reconnecting for more than 60 seconds so
far, stop reconnecting.
return null;
}
}
})
.build();

Como alternativa, o código pode ser escrito que reconecta o cliente manualmente,
conforme demonstrado na seção a seguir.

Reconectar manualmente
O código a seguir demonstra uma abordagem típica de reconexão manual:

1. Uma função (nesse caso, a start função) é criada para iniciar a conexão.
2. Chame a start função no manipulador de eventos da onclose conexão.

JavaScript

async function start() {


try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};

connection.onclose(async () => {
await start();
});

As implementações de produção normalmente usam um back-off exponencial ou


tentam novamente um número especificado de vezes.

Guia de suspensão do navegador


Alguns navegadores têm um recurso de congelamento ou suspensão de guias para
reduzir o uso de recursos do computador para guias inativas. Isso pode fazer com que
SignalR as conexões sejam fechadas e podem resultar em uma experiência indesejada
do usuário. Os navegadores usam heurística para descobrir se uma guia deve ser
colocada para dormir, como:

Reprodução de áudio
Segurando um bloqueio da Web
Segurando um IndexedDB bloqueio
Estar conectado a um dispositivo USB
Capturando vídeo ou áudio
Sendo espelhado
Capturando uma janela ou exibição

A heurística do navegador pode mudar ao longo do tempo e pode diferir entre


navegadores. Verifique a matriz de suporte e descubra qual método funciona melhor
para seus cenários.

Para evitar colocar um aplicativo em suspensão, o aplicativo deve disparar uma das
heurísticas que o navegador usa.
O exemplo de código a seguir mostra como usar um Bloqueio da Web para manter
uma guia ativada e evitar um fechamento de conexão inesperado.

JavaScript

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
const promise = new Promise((res) => {
lockResolver = res;
});

navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {


return promise;
});
}

Para o exemplo de código anterior:

Os bloqueios da Web são experimentais. A verificação condicional confirma que o


navegador dá suporte a Bloqueios da Web.
O resolvedor lockResolver de promessas é armazenado para que o bloqueio possa
ser liberado quando for aceitável que a guia durma.
Ao fechar a conexão, o bloqueio é liberado chamando lockResolver() . Quando o
bloqueio é liberado, a guia tem permissão para dormir.

Recursos adicionais
Exibir ou baixar código de exemplo (como baixar)
Referência da API JavaScript
Tutorial do JavaScript
Tutorial de WebPack e TypeScript
Hubs
Cliente .NET
Publicar no Azure
Solicitações entre origens (CORS)
Documentação sem servidor do Serviço do Azure SignalR
Solucionar erros de conexão
@microsoft/signalr package
Reference  

Neste artigo
Classes
Interfaces
Type Aliases
Enums

Classes
DefaultHttpClient Default implementation of HttpClient.

AbortError Error thrown when an action is aborted.

HttpError Error thrown when an HTTP request fails.

TimeoutError Error thrown when a timeout elapses.

FetchHttpClient

HeaderNames

HttpClient Abstraction over an HTTP client. This class provides an


abstraction over an HTTP client so that a different
implementation can be provided on different platforms.

HttpResponse Represents an HTTP response.

HubConnection Represents a connection to a SignalR Hub.

HubConnectionBuilder A builder for configuring HubConnection instances.

JsonHubProtocol Implements the JSON Hub Protocol.

NullLogger A logger that does nothing when log messages are sent to it.

Subject Stream implementation to stream items to the server.

XhrHttpClient

Interfaces
AbortSignal Represents a signal that can be monitored to determine if a
request has been aborted.
HttpRequest Represents an HTTP request.

IHttpConnectionOptions Options provided to the 'withUrl' method on


HubConnectionBuilder to configure options for the HTTP-based
transports.

CancelInvocationMessage A hub message sent to request that a streaming invocation be


canceled.

CloseMessage A hub message indicating that the sender is closing the


connection. If error is defined, the sender is closing the
connection due to an error.

CompletionMessage A hub message representing the result of an invocation.

HubInvocationMessage Defines properties common to all Hub messages relating to a


specific invocation.

HubMessageBase Defines properties common to all Hub messages.

IHubProtocol A protocol abstraction for communicating with SignalR Hubs.

InvocationMessage A hub message representing a non-streaming invocation.

MessageHeaders Defines a dictionary of string keys and string values representing


headers attached to a Hub message.

PingMessage A hub message indicating that the sender is still active.

StreamInvocationMessage A hub message representing a streaming invocation.

StreamItemMessage A hub message representing a single item produced as part of a


result stream.

ILogger An abstraction that provides a sink for diagnostic messages.

IRetryPolicy An abstraction that controls when the client attempts to


reconnect and how many times it does so.

RetryContext

ITransport An abstraction over the behavior of transports. This is designed


to support the framework and not intended for use by
applications.

IStreamResult Defines the result of a streaming hub method.

IStreamSubscriber Defines the expected type for a receiver of results streamed by


the server.

ISubscription An interface that allows an IStreamSubscriber to be disconnected


from a stream.
Type Aliases
HubMessage Union type of all known Hub messages.

Enums
HubConnectionState Describes the current state of the HubConnection to the server.

MessageType Defines the type of a Hub Message.

LogLevel Indicates the severity of a log message. Log Levels are ordered in
increasing severity. So Debug is more severe than Trace , etc.

HttpTransportType Specifies a specific HTTP transport type.

TransferFormat Specifies the transfer format for a connection.


SignalR ASP.NET Core hospedagem e
dimensionamento
Artigo • 04/01/2023 • 7 minutos para o fim da leitura

Por Andrew Stanton-Nurse , Brady Gaster e Tom Dykstra

Este artigo explica as considerações de hospedagem e dimensionamento para


aplicativos de alto tráfego que usam ASP.NET Core SignalR.

Sessões pegajosas
SignalR requer que todas as solicitações HTTP para uma conexão específica sejam
tratadas pelo mesmo processo de servidor. Quando SignalR estiver em execução em um
farm de servidores (vários servidores), "sessões pegajosas" devem ser usadas. "Sessões
pegajosas" também são chamadas de afinidade de sessão por alguns balanceadores de
carga. Serviço de Aplicativo do Azure usa o ARR (Application Request Routing) para
rotear solicitações. Habilitar a configuração "Afinidade ARR" em seu Serviço de
Aplicativo do Azure habilitará "sessões pegajosas". As únicas circunstâncias em que
sessões pegajosas não são necessárias são:

1. Ao hospedar em um único servidor, em um único processo.


2. Ao usar o Serviço do Azure SignalR .
3. Quando todos os clientes são configurados para usar somente WebSockets , e a
configuração SkipNegotiation está habilitada na configuração do cliente.

Em todas as outras circunstâncias (incluindo quando o backplane redis é usado), o


ambiente do servidor deve ser configurado para sessões autoadesivas.

Para obter diretrizes sobre como configurar Serviço de Aplicativo do Azure,


SignalRconsulte Publicar um aplicativo ASP.NET Core SignalR para Serviço de Aplicativo
do Azure. Para obter diretrizes sobre como configurar sessões pegajosas para Blazor
aplicativos que usam o Serviço do AzureSignalR, consulte Host e implante ASP.NET Core
Blazor Server.

Recursos de conexão TCP


O número de conexões TCP simultâneas que um servidor Web pode dar suporte é
limitado. Os clientes HTTP padrão usam conexões efêmeras . Essas conexões podem ser
fechadas quando o cliente fica ocioso e reaberto posteriormente. Por outro lado, uma
SignalR conexão é persistente. SignalR as conexões permanecem abertas mesmo
quando o cliente fica ocioso. Em um aplicativo de alto tráfego que atende a muitos
clientes, essas conexões persistentes podem fazer com que os servidores atinjam o
número máximo de conexões.

As conexões persistentes também consomem alguma memória adicional, para


acompanhar cada conexão.

O uso intenso de recursos SignalR relacionados à conexão pode afetar outros


aplicativos Web hospedados no mesmo servidor. Quando SignalR abre e mantém as
últimas conexões TCP disponíveis, outros aplicativos Web no mesmo servidor também
não têm mais conexões disponíveis para eles.

Se um servidor ficar sem conexões, você verá erros aleatórios de soquete e erros de
redefinição de conexão. Por exemplo:

An attempt was made to access a socket in a way forbidden by its access


permissions...

Para impedir que SignalR o uso de recursos cause erros em outros aplicativos Web,
execute SignalR em servidores diferentes dos outros aplicativos Web.

Para impedir que SignalR o uso de recursos cause erros em um SignalR aplicativo,
expanda verticalmente para limitar o número de conexões que um servidor precisa
manipular.

Escalar horizontalmente
Um aplicativo que usa SignalR precisa controlar todas as suas conexões, o que cria
problemas para um farm de servidores. Adicione um servidor e ele obtém novas
conexões que os outros servidores não sabem. Por exemplo, SignalR em cada servidor
no diagrama a seguir não está ciente das conexões nos outros servidores. Quando
SignalR em um dos servidores deseja enviar uma mensagem a todos os clientes, a
mensagem só vai para os clientes conectados a esse servidor.
As opções para resolver esse problema são o backplane do Serviço do Azure SignalR e
do Redis.

Serviço do Azure SignalR


O Serviço do Azure SignalR funciona como um proxy para tráfego em tempo real e
funciona como um backplane quando o aplicativo é dimensionado em vários servidores.
Sempre que um cliente inicia uma conexão com o servidor, o cliente é redirecionado
para se conectar ao serviço. O processo é ilustrado pelo seguinte diagrama:

O resultado é que o serviço gerencia todas as conexões do cliente, enquanto cada


servidor precisa apenas de um pequeno número constante de conexões com o serviço,
conforme mostrado no seguinte diagrama:
Essa abordagem de expansão tem várias vantagens em relação à alternativa de
backplane do Redis:

As sessões pegajosas, também conhecidas como afinidade de cliente, não são


necessárias, pois os clientes são redirecionados imediatamente para o Serviço do
Azure SignalR quando se conectam.
Um SignalR aplicativo pode ser expandido com base no número de mensagens
enviadas, enquanto o Serviço do Azure SignalR é dimensionado para lidar com
qualquer número de conexões. Por exemplo, pode haver milhares de clientes, mas
se apenas algumas mensagens por segundo forem enviadas, o SignalR aplicativo
não precisará ser expandido para vários servidores apenas para lidar com as
próprias conexões.
Um SignalR aplicativo não usará significativamente mais recursos de conexão do
que um aplicativo Web sem SignalR.

Por esses motivos, recomendamos o Serviço do Azure SignalR para todos os aplicativos
ASP.NET Core SignalR hospedados no Azure, incluindo Serviço de Aplicativo, VMs e
contêineres.

Para obter mais informações, consulte a documentação do Serviço do AzureSignalR.

Backplane de Redis
O 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 SignalR backplane do Redis
usa o recurso pub/sub para encaminhar mensagens para outros servidores. Quando um
cliente faz uma conexão, as informações de conexão são passadas para o backplane.
Quando um servidor deseja enviar uma mensagem a todos os clientes, ele envia para o
backplane. O backplane conhece todos os clientes conectados e em quais servidores
eles estão. Ele envia a mensagem a todos os clientes por meio de seus respectivos
servidores. Esse processo é ilustrado no seguinte diagrama:
O backplane redis é a abordagem de expansão recomendada para aplicativos
hospedados em sua própria infraestrutura. Se houver uma latência de conexão
significativa entre o data center e um data center do Azure, o Serviço do Azure SignalR
poderá não ser uma opção prática para aplicativos locais com baixa latência ou
requisitos de alta taxa de transferência.

As vantagens do Serviço do Azure SignalR observadas anteriormente são desvantagens


para o backplane do Redis:

As sessões pegajosas, também conhecidas como afinidade do cliente, são


necessárias, exceto quando ambas são verdadeiras:
Todos os clientes são configurados para usar somente WebSockets.
A configuração SkipNegotiation está habilitada na configuração do cliente.
Depois que uma conexão é iniciada em um servidor, a conexão deve
permanecer nesse servidor.
Um SignalR aplicativo deve ser expandido com base no número de clientes,
mesmo que poucas mensagens estejam sendo enviadas.
Um SignalR aplicativo usa significativamente mais recursos de conexão do que um
aplicativo Web sem SignalR.

Limitações do IIS no sistema operacional


cliente Windows
Windows 10 e Windows 8.x são sistemas operacionais cliente. O IIS em sistemas
operacionais cliente tem um limite de 10 conexões simultâneas. SignalRAs conexões
são:
Transitório e frequentemente restabelecida.
Não descartado imediatamente quando não for mais usado.

As condições anteriores tornam provável que ele atinja o limite de conexão de 10 em


um sistema operacional cliente. Quando um sistema operacional cliente é usado para
desenvolvimento, recomendamos:

Evite o IIS.
Use Kestrel ou IIS Express como destinos de implantação.

Linux com o Nginx


O seguinte contém as configurações mínimas necessárias para habilitar WebSockets,
ServerSentEvents e LongPolling para SignalR:

nginx

http {
map $http_connection $connection_upgrade {
"~*Upgrade" $http_connection;
default keep-alive;
}

server {
listen 80;
server_name example.com *.example.com;

# Configure the SignalR Endpoint


location /hubroute {
# App server url
proxy_pass http://localhost:5000;

# Configuration for WebSockets


proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cache off;
# WebSockets were implemented after http/1.0
proxy_http_version 1.1;

# Configuration for ServerSentEvents


proxy_buffering off;

# Configuration for LongPolling or if your KeepAliveInterval is longer


than 60 seconds
proxy_read_timeout 100s;

proxy_set_header Host $host;


proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

Quando vários servidores de back-end são usados, sessões pegajosas devem ser
adicionadas para impedir que SignalR as conexões alternem os servidores ao se
conectarem. Há várias maneiras de adicionar sessões pegajosas no Nginx. Duas
abordagens são mostradas abaixo, dependendo do que você tem disponível.

O seguinte é adicionado além da configuração anterior. Nos exemplos a seguir, backend


é o nome do grupo de servidores.

Com o Nginx Open Source , use ip_hash para rotear conexões para um servidor com
base no endereço IP do cliente:

nginx

http {
upstream backend {
# App server 1
server localhost:5000;
# App server 2
server localhost:5002;

ip_hash;
}
}

Com o Nginx Plus , use sticky para adicionar uma cookie às solicitações e fixar as
solicitações do usuário em um servidor:

nginx

http {
upstream backend {
# App server 1
server localhost:5000;
# App server 2
server localhost:5002;

sticky cookie srv_id expires=max domain=.example.com path=/ httponly;


}
}

Por fim, altere proxy_pass http://localhost:5000 na server seção para proxy_pass


http://backend .
Para obter mais informações sobre WebSockets no Nginx, consulte NGINX como um
Proxy WebSocket .

Para obter mais informações sobre balanceamento de carga e sessões pegajosas,


consulte o balanceamento de carga NGINX .

Para obter mais informações sobre ASP.NET Core com o Nginx, consulte o seguinte
artigo:

Host ASP.NET Core no Linux com Nginx

Provedores de backplane de terceiros SignalR


NCache
Orleans
Rebus
SQL Server

Próximas etapas
Para saber mais, consulte os recursos a seguir:

Documentação do Serviço do Azure SignalR


Configurar um backplane do Redis
Publicar um aplicativo ASP.NET Core
SignalR para Serviço de Aplicativo do
Azure
Artigo • 04/01/2023 • 3 minutos para o fim da leitura

Por Brady Gaster

Serviço de Aplicativo do Azure é um serviço de plataforma de computação em nuvem


da Microsoft para hospedar aplicativos Web, incluindo ASP.NET Core.

7 Observação

Este artigo refere-se à publicação de um aplicativo ASP.NET Core SignalR do Visual


Studio. Para obter mais informações, consulte oSignalR serviço do Azure .

Publicar o aplicativo
Este artigo aborda a publicação usando as ferramentas no Visual Studio. Visual Studio
Code usuários podem usar comandos da CLI do Azure para publicar aplicativos no
Azure. Para obter mais informações, consulte Publicar um aplicativo ASP.NET Core no
Azure com ferramentas de linha de comando.

1. Clique com o botão direito do mouse no projeto, no Gerenciador de Soluções, e


selecione Publicar.

2. Confirme se Serviço de Aplicativo e Criar novo estão selecionados na caixa de


diálogo Escolher um destino de publicação.

3. Selecione Criar Perfil na lista suspensa do botão Publicar .

Insira as informações descritas na tabela a seguir na caixa de diálogo Criar Serviço


de Aplicativo e selecione Criar.

Item Descrição

Nome Nome exclusivo do aplicativo.

Assinatura Assinatura do Azure que o aplicativo usa.

Grupo de recursos Grupo de recursos relacionados aos quais o aplicativo pertence.


Item Descrição

Plano de hospedagem Plano de preços para o aplicativo Web.

4. Selecione o Serviço do Azure SignalR na seção Dependências de Serviço.


Selecione o + botão:

5. Na caixa de diálogo Serviço do AzureSignalR, selecione Criar uma nova instância


do Serviço do AzureSignalR.

6. Forneça um nome, um grupo de recursos e um local. Retorne à caixa de diálogo


Serviço do Azure SignalR e selecione Adicionar.

O Visual Studio conclui as seguintes tarefas:

Cria um Perfil de Publicação contendo configurações de publicação.


Cria um aplicativo Web do Azure com os detalhes fornecidos.
Publica o aplicativo.
Inicia um navegador, que carrega o aplicativo Web.

O formato da URL do aplicativo é {APP SERVICE NAME}.azurewebsites.net . Por exemplo,


um aplicativo nomeado SignalRChatApp tem uma URL de
https://signalrchatapp.azurewebsites.net .

Se ocorrer um erro HTTP 502.2 – Gateway incorreto ao implantar um aplicativo


direcionado a uma versão prévia do .NET Core, consulte Implantar ASP.NET Core versão
prévia para Serviço de Aplicativo do Azure para resolvê-lo.

Configurar o aplicativo no Serviço de Aplicativo


do Azure
7 Observação

Esta seção se aplica somente a aplicativos que não usam o Serviço do Azure SignalR .

Se o aplicativo usar o Serviço do AzureSignalR, o Serviço de Aplicativo não exigirá a


configuração da Afinidade de Roteamento de Solicitação de Aplicativo (ARR) e dos
Web Sockets descritos nesta seção. Os clientes conectam seus Web Sockets ao
Serviço do Azure SignalR , não diretamente ao aplicativo.

Para aplicativos hospedados sem o Serviço do Azure SignalR , habilite:

Afinidade ARR para rotear solicitações de um usuário de volta para a mesma


instância Serviço de Aplicativo. A configuração padrão é Ativada.
Web Sockets para permitir que o transporte de Web Sockets funcione. A
configuração padrão está desativada.

1. No portal do Azure, navegue até o aplicativo Web nos Serviços de Aplicativo.


2. Abrirconfigurações gerais de configuração>.
3. Defina soquetes da Web como Ativados.
4. Verifique se a afinidade ARR está definida como Ativada.

Limites do Plano Serviço de Aplicativo


Os Web Sockets e outros transportes são limitados com base no plano de Serviço de
Aplicativo selecionado. Para obter mais informações, consulte os limites de Serviços de
Nuvem do Azure e Serviço de Aplicativo limita seções do artigo de assinatura, cotas e
restrições do Azure.

Recursos adicionais
O que é o Serviço do Azure SignalR ?
Visão geral do ASP.NET Core SignalR
Hospedar e implantar o ASP.NET Core
Publicar um aplicativo ASP.NET Core no Azure com o Visual Studio
Publicar um aplicativo ASP.NET Core no Azure com as ferramentas de linha de
comando
Hospedar e implantar aplicativos ASP.NET Core Versão Prévia no Azure
Configurar um backplane do Redis para
ASP.NET Core SignalR expansão
Artigo • 04/01/2023 • 4 minutos para o fim da leitura

Por Andrew Stanton-Nurse , Brady Gaster e Tom Dykstra ,

Este artigo explica aspectos SignalRespecíficos da configuração de um servidor Redis


a ser usado para dimensionar um aplicativo ASP.NET CoreSignalR.

Configurar um backplane do Redis


Implantar um servidor Redis.

) Importante

Para uso em produção, um backplane do Redis é recomendado somente


quando ele é executado no mesmo data center que o SignalR aplicativo. Caso
contrário, a latência de rede degrada o desempenho. Se o SignalR aplicativo
estiver em execução na nuvem do Azure, recomendamos o Serviço do Azure
SignalR em vez de um backplane redis.

Para saber mais, consulte os recursos a seguir:


SignalR ASP.NET Core hospedagem e dimensionamento de produção
Documentação de Redis
Documentação do Cache Redis do Azure

SignalR No aplicativo, instale o seguinte pacote NuGet:


Microsoft.AspNetCore.SignalR.StackExchangeRedis

Startup.ConfigureServices No método, chameAddStackExchangeRedis:

C#

services.AddSignalR().AddStackExchangeRedis("
<your_Redis_connection_string>");

Configure as opções conforme necessário:

A maioria das opções pode ser definida na cadeia de conexão ou no objeto


ConfigurationOptions . Opções especificadas em ConfigurationOptions
substituir as definidas na cadeia de conexão.

O exemplo a seguir mostra como definir opções no ConfigurationOptions objeto.


Este exemplo adiciona um prefixo de canal para que vários aplicativos possam
compartilhar a mesma instância do Redis, conforme explicado na etapa a seguir.

C#

services.AddSignalR()
.AddStackExchangeRedis(connectionString, options => {
options.Configuration.ChannelPrefix = "MyApp";
});

No código anterior, options.Configuration é inicializado com o que foi


especificado na cadeia de conexão.

Para obter informações sobre as opções do Redis, consulte a documentação do


StackExchange Redis .

Se você estiver usando um servidor Redis para vários SignalR aplicativos, use um
prefixo de canal diferente para cada SignalR aplicativo.

Definir um prefixo de canal isola um SignalR aplicativo de outros que usam


prefixos de canal diferentes. Se você não atribuir prefixos diferentes,

Você também pode gostar