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 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 informações sobre a versão vigente, confira a Versão do .NET 8 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

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:

Considere uma página básica:

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

O código anterior 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:

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étodo OnGet exibe a Razor Page CreateModel.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 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.
    [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:

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

Home page

Index.cshtml é a página inicial:

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

<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>

O HTML renderizado:

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

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

Formulário da exibição de filmes com vários erros de validação do lado do cliente do jQuery

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 restante 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:

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 restante do namespace gerado (a parte do sufixo) é o caminho relativo separado por ponto entre a pasta que contém _ViewImports.cshtmle a pasta que contém a página.

Por exemplo, a classe PageModelPages/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.cshtmlRazor Page é o mesmo que a classe PageModel.

@namespacetambé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>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 :

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

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

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

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

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

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

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

Considere uma página básica:

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

O código anterior 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:

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étodo OnGet exibe a Razor Page CreateModel.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 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.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _context.Customers.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:

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>

Home page

Index.cshtml é a página inicial:

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

<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>

O HTML renderizado:

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

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

Formulário da exibição de filmes com vários erros de validação do lado do cliente do jQuery

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 restante do namespace gerado (a parte do sufixo) é o caminho relativo separado por ponto entre a pasta que contém _ViewImports.cshtmle a pasta que contém a página.

Por exemplo, a classe PageModelPages/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.cshtmlRazor Page é o mesmo que a classe PageModel.

@namespacetambé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>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 :

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

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