Introdução ao Razor Pages no ASP.NET Core
Por Rick Anderson, Dave Brock e Kirk Larkin
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para a versão atual, consulte a versão .NET 9 deste artigo.
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 2022 com a carga de trabalho do ASP.NET e desenvolvimento Web.
- SDK do .NET 6.0
Criar um projeto do Razor Pages
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
:
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.
@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
:
@page
@using RazorPagesIntro.Pages
@model Index2Model
<h2>Separate page model</h2>
<p>
@Model.Message
</p>
O modelo de página Pages/Index2.cshtml.cs
:
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 |
/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 Program.cs.
O banco de dados na memória requer o pacote NuGet Microsoft.EntityFrameworkCore.InMemory
.
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:
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:
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
:
@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
:
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 manipuladorOnPostAsync
, 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étodoOnGet
exibe a Razor PageCreate.cshtml
.OnPost
para manipular envios de formulário.
O sufixo de nomenclatura Async
é opcional, mas costuma ser 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:
[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
:
@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
:
<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
ouRedirectToRoute
(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. RetornarPage
é semelhante a como as ações em controladores retornamView
.PageResult
é o tipo de retorno padrão para um método de manipulador. Um método de manipulador que retornavoid
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.
[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 método de manipulador
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:
[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.
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
:
[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
:
@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 modeloCustomer.Name
. @addTagHelper
disponibiliza os Auxiliares de Marca.
A home page
Index.cshtml
é a home page.
@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
):
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:
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
O auxiliar de marcas de âncora <a /a>
usou o atributo asp-route-{value}
para gerar um link para a página Edit. O link contém dados de rota com a ID de contato. Por exemplo, 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:
<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
O HTML renderizado:
<button type="submit" formaction="/Customers?id=1&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 atributoasp-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.
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
@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:
@page "{id:int?}"
O arquivo Edit.cshtml.cs
:
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
:
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
:
@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:
<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
:
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
eMinimumLength
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 saber mais, veja:
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):
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:
<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />
No seguinte exemplo, o nome do assembly do aplicativo é WebApp
:
<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
.
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:
/* /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:
<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 AspNetCore.SassCompiler
, 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:
<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.
<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:
<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 rest dos ativos do aplicativo, no caminho _content
:
<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:
<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):
<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
:
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
:
<!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
:
@{
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
:
@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:
@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 rest do namespace gerado (a parte do sufixo) é o caminho relativo separado por ponto entre a pasta que contém _ViewImports.cshtml
e a pasta que contém a página.
Por exemplo, a classe PageModel
Pages/Customers/Edit.cshtml.cs
define explicitamente o namespace:
namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;
public EditModel(AppDbContext db)
{
_db = db;
}
// Code removed for brevity.
O arquivo Pages/_ViewImports.cshtml
define o seguinte namespace:
@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
:
@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:
@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
:
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");
}
}
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) | Page |
---|---|
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:
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
:
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:
<h1>@Model.Title</h1>
No layout, o título é lido a partir do dicionário ViewData:
<!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
:
public class CreateDotModel : PageModel
{
private readonly AppDbContext _db;
public CreateDotModel(AppDbContext db)
{
_db = db;
}
[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
A marcação a seguir no arquivo Pages/Customers/Index.cshtml
exibe o valor de Message
usando TempData
.
<h3>Msg: @Model.Message</h3>
O modelo de página Pages/Customers/Index.cshtml.cs
aplica o atributo [TempData]
à propriedade Message
.
[TempData]
public string Message { get; set; }
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
:
@page
@model CreateFATHModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></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 :
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
.
<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
.
@page "{handler?}"
@model CreateRouteModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></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.
Colocação dos arquivos JavaScript (JS)
A colocação de arquivos JavaScript (JS) para páginas e exibições é 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áginaIndex
de um aplicativo Razor Pages emPages/Index.cshtml
.Views/Home/Index.cshtml.js
para a exibiçãoIndex
de um aplicativo MVC emViews/Home/Index.cshtml
.
Arquivos JS colocalizados são endereçáveis publicamente usando o caminho para o arquivo no projeto:
Páginas e exibições de um arquivo de scripts colocados 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
oucshtml
.
Exemplo do Razor Pages:
Um arquivo JS para a página
Index
é colocado na pastaPages
(Pages/Index.cshtml.js
) ao lado da páginaIndex
(Pages/Index.cshtml
). Na páginaIndex
, o script é referenciado no caminho na pastaPages
:@section Scripts { <script src="~/Pages/Index.cshtml.js"></script> }
- O espaço reservado
O layout Pages/Shared/_Layout.cshtml
padrão pode ser configurado para incluir arquivos JS colocados, eliminando a necessidade de configurar cada página individualmente:
<script asp-src-include="@(ViewContext.View.Path).js"></script>
O download de exemplo usa o snippet de código anterior para incluir arquivos JS colocados no layout padrão.
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
.
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\Components\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
oucshtml
.
- O espaço reservado
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:
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:
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):
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
- Visual Studio 2019 16.4 ou posterior com a carga de trabalho de desenvolvimento da Web e do ASP.NET
- SDK do .NET Core 3.1
- Visual Studio 2019 16.8 ou posterior com a carga de trabalho do ASP.NET e desenvolvimento Web
- SDK do .NET 5.0
Criar um projeto do Razor Pages
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 Startup.cs
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
@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
. @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
:
@page
@using RazorPagesIntro.Pages
@model Index2Model
<h2>Separate page model</h2>
<p>
@Model.Message
</p>
O modelo de página Pages/Index2.cshtml.cs
:
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 |
/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 marcas e auxiliares HTML funcionam todos apenas 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
.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));
services.AddRazorPages();
}
O modelo de dados:
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:
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Models;
namespace RazorPagesContacts.Data
{
public class CustomerDbContext : DbContext
{
public CustomerDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<Customer> Customers { get; set; }
}
}
O arquivo de exibição Pages/Create.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/Create.cshtml.cs
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using RazorPagesContacts.Models;
using System.Threading.Tasks;
namespace RazorPagesContacts.Pages.Customers
{
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");
}
}
}
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 manipuladorOnPostAsync
, 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étodoOnGet
exibe a Razor PageCreateModel.cshtml
.OnPost
para manipular envios de formulário.
O sufixo de nomenclatura Async
é opcional, mas costuma ser 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:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Customers.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/Create.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/Create.cshtml
:
<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
ouRedirectToRoute
(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. RetornarPage
é semelhante a como as ações em controladores retornamView
.PageResult
é o tipo de retorno padrão para um método de manipulador. Um método de manipulador que retornavoid
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.
public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } _context.Customers.Add(Customer); await _context.SaveChangesAsync(); return RedirectToPage("./Index"); }
- O método de manipulador
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:
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");
}
}
[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.
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
:
[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/Create.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 modeloCustomer.Name
. @addTagHelper
disponibiliza os Auxiliares de Marca.
A home page
Index.cshtml
é a home page.
@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>
@foreach (var contact in Model.Customer)
{
<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
):
public class IndexModel : PageModel
{
private readonly CustomerDbContext _context;
public IndexModel(CustomerDbContext context)
{
_context = context;
}
public IList<Customer> Customer { get; set; }
public async Task OnGetAsync()
{
Customer = await _context.Customers.ToListAsync();
}
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var contact = await _context.Customers.FindAsync(id);
if (contact != null)
{
_context.Customers.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
}
O arquivo Index.cshtml
contém o seguinte markup:
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
O auxiliar de marcas de âncora <a /a>
usou o atributo asp-route-{value}
para gerar um link para a página Edit. O link contém dados de rota com a ID de contato. Por exemplo, 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:
<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
O HTML renderizado:
<button type="submit" formaction="/Customers?id=1&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 atributoasp-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.
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var contact = await _context.Customers.FindAsync(id);
if (contact != null)
{
_context.Customers.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
@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
<div asp-validation-summary="All"></div>
<input asp-for="Customer.Id" type="hidden" />
<div>
<label asp-for="Customer.Name"></label>
<div>
<input asp-for="Customer.Name" />
<span asp-validation-for="Customer.Name"></span>
</div>
</div>
<div>
<button type="submit">Save</button>
</div>
</form>
A primeira linha contém a diretiva @page "{id:int}"
. A restrição de roteamento "{id:int}"
informa à página para aceitar solicitações para a página que contêm dados da rota int
. Se uma solicitação para a página não contém dados de rota que podem ser convertidos em um int
, o runtime retorna um erro HTTP 404 (não encontrado). Para tornar a ID opcional, acrescente ?
à restrição de rota:
@page "{id:int?}"
O arquivo Edit.cshtml.cs
:
public class EditModel : PageModel
{
private readonly CustomerDbContext _context;
public EditModel(CustomerDbContext context)
{
_context = context;
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Customer = await _context.Customers.FindAsync(id);
if (Customer == null)
{
return RedirectToPage("./Index");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Customer).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw new Exception($"Customer {Customer.Id} not found!");
}
return RedirectToPage("./Index");
}
}
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
:
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
:
@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:
<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
:
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
eMinimumLength
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 saber mais, veja:
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
:
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
:
<!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
:
@{
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
:
@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:
@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 rest do namespace gerado (a parte do sufixo) é o caminho relativo separado por ponto entre a pasta que contém _ViewImports.cshtml
e a pasta que contém a página.
Por exemplo, a classe PageModel
Pages/Customers/Edit.cshtml.cs
define explicitamente o namespace:
namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;
public EditModel(AppDbContext db)
{
_db = db;
}
// Code removed for brevity.
O arquivo Pages/_ViewImports.cshtml
define o seguinte namespace:
@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/Create.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/Create.cshtml
atualizado com _ViewImports.cshtml
e o arquivo de layout anterior:
@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
:
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) | Page |
---|---|
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:
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
:
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:
<h1>@Model.Title</h1>
No layout, o título é lido a partir do dicionário ViewData:
<!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
:
public class CreateDotModel : PageModel
{
private readonly AppDbContext _db;
public CreateDotModel(AppDbContext db)
{
_db = db;
}
[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
A marcação a seguir no arquivo Pages/Customers/Index.cshtml
exibe o valor de Message
usando TempData
.
<h3>Msg: @Model.Message</h3>
O modelo de página Pages/Customers/Index.cshtml.cs
aplica o atributo [TempData]
à propriedade Message
.
[TempData]
public string Message { get; set; }
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
:
@page
@model CreateFATHModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></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 :
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
.
<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
.
@page "{handler?}"
@model CreateRouteModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></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:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.RootDirectory = "/MyPages";
options.Conventions.AuthorizeFolder("/MyPages/Admin");
});
}
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:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesAtContentRoot();
}
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):
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesRoot("/path/to/razor/pages");
}
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