Introdução às Razor páginas no ASP.NET Core

Por Rick Anderson, Dave Brock e Kirk Larkin

Razor As páginas podem tornar os cenários focados em página de codificação mais fáceis e produtivos do que 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 fornece uma introdução ao Razor Pages. Este não é um tutorial passo a passo. Se você encontrar algumas das seções muito avançadas, consulte Introdução com Razor Páginas. Para obter uma visão geral do ASP.NET Core, consulte a Introdução ao ASP.NET Core.

Pré-requisitos

Criar um Razor projeto de Páginas

Consulte Introdução com Razor Páginas para obter instruções detalhadas sobre como criar um Razor projeto de Páginas.

Razor Páginas

Razor As páginas estão habilitadas 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 parece muito com um Razor arquivo de exibição usado em um aplicativo ASP.NET Core com controladores e exibições. O que o torna diferentes é a diretiva @page. @page torna o arquivo em uma ação MVC, o que significa que ele lida diretamente com solicitações, sem passar por um controlador. @page deve ser a primeira Razor diretiva em uma página. @page afeta o comportamento de outras Razor construções. Razor Os nomes de arquivo de páginas têm um .cshtml sufixo.

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 Pages/Index2.cshtml.cs modelo de página:

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 PageModel arquivo de classe tem o mesmo nome que o Razor arquivo page com .cs acrescentado. Por exemplo, a página anterior Razor é Pages/Index2.cshtml. O arquivo que contém a PageModel classe é nomeado Pages/Index2.cshtml.cs.

As associações de caminhos de URL para páginas são determinadas pelo local da página no sistema de arquivos. A tabela a seguir mostra um Razor caminho de página 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 Razor arquivos de Páginas na pasta Páginas por padrão.
  • Index é a página padrão quando uma URL não inclui uma página.

Escrever um formulário básico

Razor As páginas foram projetadas para tornar os padrões comuns usados com navegadores da Web fáceis de implementar ao criar um aplicativo. Os auxiliares de model binding, Tag Helpers e HTML trabalham com as propriedades definidas em uma Razor classe 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 Microsoft.EntityFrameworkCore.InMemory pacote NuGet.

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 { get; set; }
    }
}

O Pages/Create.cshtml arquivo de exibição:

@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 Pages/Create.cshtml.cs modelo de página:

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

        _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 OnGet método exibe a CreateModel.cshtmlRazor Página.
  • OnPost para manipular envios de formulário.

O sufixo de nomenclatura Async é opcional, mas geralmente é usado por convenção para funções assíncronas. O código anterior é típico para Razor Páginas.

Se você estiver familiarizado com ASP.NET aplicativos usando controladores e exibições:

  • O OnPostAsync código no exemplo anterior é semelhante ao código típico do controlador.
  • A maioria dos primitivos do MVC, como associação de modelo, validação e resultados de ação, funcionam da mesma forma com Controladores e Razor Páginas.

O método OnPostAsync anterior:

[BindProperty]
public Customer Customer { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _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 Pages/Create.cshtml arquivo de exibição:

@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 HTML renderizado 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 OnPostAsync método manipulador chama o RedirectToPage método auxiliar. 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 páginas. Na amostra anterior, ele redireciona para a página de Índice raiz (/Index). RedirectToPage é detalhado na seção geração de URL para Páginas .
  • Com erros de validação que são passados para o servidor:

    • O OnPostAsync método manipulador chama o Page método auxiliar. 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 resulta em ModelState.IsValid retornando false. Neste exemplo, nenhum erro de validação é exibido no cliente. A entrega de erros de validação será abordada posteriormente neste documento.
    [BindProperty]
    public Customer Customer { get; set; }
    
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _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 Customer propriedade usa [BindProperty] o atributo para aceitar a associação de modelo:

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

        _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

[BindProperty]não deve ser usado em modelos que contenham propriedades que não devem ser alteradas pelo cliente. Para obter mais informações, consulte Overposting.

Razor As páginas, por padrão, associam propriedades somente a verbos nãoGET verbos. 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 GET associação é útil ao abordar cenários que dependem de valores de cadeia de caracteres de consulta ou de rota.

Para associar uma propriedade em GET solicitações, defina a [BindProperty] propriedade do SupportsGet atributo como true:

[BindProperty(SupportsGet = true)]

Para obter mais informações, consulte ASP.NET Core Community Standup: Associar na discussão GET (YouTube).

Examinando o Pages/Create.cshtml arquivo de exibição:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>
  • No código anterior, o auxiliar<input asp-for="Customer.Name" /> de marca de entrada associa o elemento HTML <input> à expressão de Customer.Name modelo.
  • @addTagHelper disponibiliza auxiliares de marca.

Home page

Index.cshtml é a home page:

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customer)
            {
                <tr>
                    <td> @contact.Id  </td>
                    <td>@contact.Name</td>
                    <td>
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <button type="submit" asp-page-handler="delete"
                                asp-route-id="@contact.Id">delete
                        </button>
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

A classe associada PageModel (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 Index.cshtml arquivo contém a seguinte marcação:

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

O <a /a>Auxiliar de Marca de Âncora usou o asp-route-{value} atributo 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 permitem que o código do lado do servidor participe na criação e renderização de elementos HTML em Razor arquivos.

O Index.cshtml arquivo contém marcação para criar um botão de exclusão para cada contato do 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 excluir é renderizado em HTML, sua formação inclui parâmetros para:

  • A ID de contato do cliente, especificada pelo asp-route-id atributo.
  • O handler, especificado pelo asp-page-handler atributo.

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 a 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 "{id:int}" de roteamento informa à página para aceitar solicitações para a página que contêm int dados de rota. 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 System.ComponentModel.DataAnnotations namespace fornece um conjunto de atributos de validação internos que são aplicados declarativamente 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 Customer modelo:

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 Create.cshtml arquivo de exibição:

@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 scripts de validação jQuery e jQuery.

  • Usa os <div />auxiliares de marca e <span /> de marca 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". Se o JavaScript estiver habilitado no cliente, o navegador exibirá o erro sem postar no servidor.

O [StringLength(10)] atributo é gerado data-val-length-max="10" no HTML renderizado. data-val-length-max impede que os 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 um comprimento máximo de 10".

Considere o seguinte Movie modelo:

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 aplicado às propriedades do modelo às quais são aplicados:

  • Os Required atributos e os MinimumLength atributos indicam que uma propriedade deve ter um valor, mas nada impede que um usuário insira espaço em branco para atender a essa validação.

  • O atributo RegularExpression é usado para limitar quais caracteres podem ser inseridos. No código anterior, "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 e números especiais em 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 StringLength atributo define o comprimento máximo de uma propriedade de cadeia de caracteres e, opcionalmente, seu comprimento 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 Movie modelo mostra erros com valores inválidos:

Movie view form with multiple jQuery client-side validation errors

Para obter mais informações, consulte:

Isolamento de CSS

Isolar 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 complementar que corresponda .cshtml.css ao nome do .cshtml arquivo. No exemplo a seguir, um Index.cshtml.css arquivo fornece estilos CSS que são aplicados somente à Index.cshtml página ou exibição.

Pages/Index.cshtml.css (Razor Páginas) ou Views/Index.cshtml.css (MVC):

h1 {
    color: red;
}

O isolamento CSS ocorre em tempo de build. A estrutura reescreve seletores CSS para corresponder à marcação renderizada pelas páginas ou exibições do aplicativo. Os estilos CSS reescritos são agrupados 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.

<head> No conteúdo 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 exemplo a seguir, o nome do assembly do aplicativo é WebApp:

<link rel="stylesheet" href="WebApp.styles.css" />

Os estilos definidos em um arquivo CSS com escopo só são aplicados à saída renderizada do arquivo correspondente. No exemplo anterior, quaisquer h1 declarações CSS definidas em outro lugar no aplicativo não entram em conflito com o Indexestilo de título '. Regras de herança e em cascata de estilo CSS permanecem em vigor para arquivos CSS com escopo. Por exemplo, estilos aplicados diretamente a um <h1> elemento no Index.cshtml arquivo substituem os estilos do arquivo CSS com escopo em Index.cshtml.css.

Observação

Para garantir o isolamento de estilo CSS quando ocorre o agrupamento, não há suporte para a importação de CSS em Razor blocos de código.

O isolamento CSS só se aplica a elementos HTML. Não há suporte para isolamento CSS para Auxiliares de Marca.

Dentro do arquivo CSS empacotado, cada página, exibição ou Razor componente está associado a um identificador de escopo no formato b-{STRING}, em que o {STRING} espaço reservado é uma cadeia de caracteres de dez caracteres gerada pela estrutura. O exemplo a seguir fornece o estilo do elemento anterior <h1> na Index página de um Razor aplicativo Pages:

/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
    color: red;
}

Index Na página em que o estilo CSS é aplicado do arquivo empacotado, o identificador de escopo é acrescentado como um atributo HTML:

<h1 b-3xxtam6d07>

O identificador é exclusivo de 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 de ativos da Web estáticos.

Se outros projetos forem utilizados, como NuGet pacotes ou Razor bibliotecas de classes, o arquivo empacotado:

  • Faz referência aos estilos que usam importações de CSS.
  • Não é publicado como um ativo web estático do aplicativo que consome os estilos.

Suporte ao pré-processador CSS

Os pré-processadores CSS são úteis para melhorar o desenvolvimento do CSS utilizando recursos como variáveis, aninhamento, módulos, mixinas e herança. Embora o isolamento CSS não dê suporte nativo a pré-processadores CSS, como Sass ou Less, a integração de pré-processadores CSS é perfeita, desde que a compilação do pré-processador ocorra antes que a estrutura reescreva os seletores CSS durante o processo de build. Usando Visual Studio, por exemplo, configure a compilação de pré-processador existente como uma tarefa Before Build no Gerenciador de Visual Studio Gerenciador de Tarefas.

Muitos pacotes de NuGet de terceiros, comoDelegate.SassBuilder, podem compilar arquivos SASS/SCSS no início do processo de build antes que o isolamento do CSS ocorra e nenhuma configuração adicional seja necessária.

Configuração de isolamento do CSS

O isolamento do 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 do identificador de escopo

Nesta seção, o {Pages|Views} espaço reservado é Pages para Razor aplicativos Pages ou Views para aplicativos MVC.

Por padrão, os identificadores de escopo usam o formato b-{STRING}, em que o {STRING} espaço reservado é 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 alterar seu identificador de escopo de b-{STRING} para custom-scope-identifier.

Use identificadores de escopo para obter herança com arquivos CSS com escopo. No exemplo de arquivo de projeto a seguir, um BaseView.cshtml.css arquivo contém estilos comuns entre exibições. Um DerivedView.cshtml.css arquivo 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 StaticWebAssetBasePath propriedade para alterar o caminho padrão. O exemplo a seguir coloca o arquivo CSS com escopo e o restante dos ativos do aplicativo no _content caminho:

<PropertyGroup>
  <StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

Desabilitar agrupamento automático

Para recusar como a estrutura publica e carrega arquivos com escopo no runtime, use a DisableScopedCssBundling propriedade. Ao usar essa propriedade, outras ferramentas ou processos são responsáveis por tirar os arquivos CSS isolados do obj diretório e publicar e carregá-los em runtime:

<PropertyGroup>
  <DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Razor Suporte à RCL (biblioteca de classes)

Quando uma Razor RCL (biblioteca de classes) fornece estilos isolados, o <link> atributo da href marca aponta para {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css, onde os espaços reservados são:

  • {STATIC WEB ASSET BASE PATH}: o caminho de base de ativos da Web estático.
  • {PACKAGE ID}: o identificador do pacote da biblioteca. O identificador de pacote padrão será o nome do assembly do projeto se o identificador do pacote não for especificado no arquivo de projeto.

No exemplo a seguir:

  • O caminho de base de ativos da Web estático é _content/ClassLib.
  • O nome do assembly da biblioteca de classes é ClassLib.

Pages/Shared/_Layout.cshtml (Razor Páginas) 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 Blazor o isolamento CSS, consulte ASP.NET Core Blazor isolamento do CSS.

Manipular solicitações HEAD com um fallback de manipulador OnGet

HEAD solicitações que 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!");
}

Razor As páginas voltarão a chamar o OnGet manipulador se nenhum OnHead manipulador for definido.

XSRF/CSRF e Razor páginas

Razor As páginas são protegidas pela validação antiforgeria. O FormTagHelper injeta tokens antiforgery em elementos de formulário HTML.

Usando layouts, parciais, modelos e auxiliares de marca com Razor páginas

As páginas funcionam com todos os recursos do mecanismo de exibição Razor . Layouts, parciais, modelos, Auxiliares _ViewStart.cshtmlde Marca e _ViewImports.cshtml funcionam da mesma maneira que funcionam para exibições convencionais Razor .

Organizaremos essa página aproveitando alguns desses recursos.

Adicionar uma página de layout a 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 Razor página é renderizado onde @RenderBody() é chamado.

Para obter mais informações, consulte a página de layout.

A propriedade Layout está 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 Páginas/Compartilhado pode ser usado de qualquer Razor página na pasta Páginas .

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. Razor As páginas são destinadas a depender da hierarquia de pastas, não de convenções de caminho.

Exibir a pesquisa de uma Razor página inclui a pasta Páginas . Os layouts, modelos e parciais usados com controladores MVC e exibições convencionais Razorapenas funcionam.

Adicione um Pages/_ViewImports.cshtml arquivo:

@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 @namespace diretiva definida em uma página:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

A @namespace diretiva define o namespace da página. A diretiva @model não precisa incluir o namespace.

Quando a @namespace diretiva está contida _ViewImports.cshtml, o namespace especificado fornece o prefixo para o namespace gerado na Página que importa a @namespace diretiva. O restante do namespace gerado (a parte do sufixo) é o caminho relativo separado por ponto entre a pasta que contém _ViewImports.cshtml e a pasta que contém a página.

Por exemplo, a PageModel classe Pages/Customers/Edit.cshtml.cs define explicitamente o namespace:

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

O Pages/_ViewImports.cshtml arquivo define o seguinte namespace:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

O namespace gerado para a Pages/Customers/Edit.cshtmlRazor Página é o mesmo que a PageModel classe.

@namespacetambém funciona com exibições convencionais 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 atualizado Pages/Create.cshtml 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, os _ViewImports.cshtml auxiliares de marca e namespace importados. O arquivo de layout importou os arquivos JavaScript.

O Razor projeto inicial 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 em 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

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

O Pages/Customers/Create.cshtml redirecionamento e Pages/Customers/Edit.cshtml as páginas após o Pages/Customers/Index.cshtml ê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 Pages/Customers/Index.cshtml página. Por exemplo:

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

O nome /Index absoluto da página é usado para gerar URLs para a Pages/Index.cshtml página. 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 de geração de URL anteriores oferecem opções aprimoradas e funcionalidades funcionais durante a codificação de 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 index é selecionada usando parâmetros diferentes RedirectToPage em Pages/Customers/Create.cshtml.

RedirectToPage(x) Página
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index"); Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index

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 entre páginas em uma pasta:

  • Renomear uma pasta não interrompe os links relativos.
  • Os links não estão quebrados 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 As áreas em ASP.NET Core e as convenções de aplicativo eRazor rotas de páginas no ASP.NET Core.

Atributo ViewData

Os dados podem ser passados para uma página com ViewDataAttribute. As propriedades com o [ViewData] atributo têm seus valores armazenados e carregados do ViewDataDictionary.

No exemplo a seguir, aplica o AboutModel[ViewData] atributo à Title propriedade:

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

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, quando 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 Pages/Customers/Index.cshtml arquivo exibe o valor de Message uso TempData.

<h3>Msg: @Model.Message</h3>

O Pages/Customers/Index.cshtml.cs modelo de página aplica o [TempData] atributo à Message propriedade.

[TempData]
public string Message { get; set; }

Para obter mais informações, consulte 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 gostar da cadeia de caracteres ?handler=JoinList de consulta na URL, altere a rota para colocar o nome do manipulador na parte de caminho da URL. A rota pode ser personalizada adicionando um modelo de rota entre aspas duplas após a @page diretiva.

@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 configurações avançadas

A configuração e as configurações nas seções a seguir não são exigidas pela maioria dos aplicativos.

Para configurar opções avançadas, use a AddRazorPages sobrecarga 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 diretório raiz para páginas ou adicione convenções de modelo de aplicativo para páginas. Para obter mais informações sobre convenções, consulte Razor as convenções de autorização do Pages.

Para pré-compilar exibições, consulte Razor a compilação de exibição.

Especifique que Razor as páginas estão na raiz do conteúdo

Por padrão, Razor as páginas têm raiz no diretório /Pages . Adicione WithRazorPagesAtContentRoot para especificar que suas Razor Páginas 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();

Especifique que Razor as páginas estão em um diretório raiz personalizado

Adicione WithRazorPagesRoot para especificar que Razor as Páginas 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 Razor projeto de Páginas

Consulte Introdução com Razor Páginas para obter instruções detalhadas sobre como criar um Razor projeto de Páginas.

Razor Páginas

Razor As páginas estão habilitadas 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 parece muito com um Razor arquivo de exibição 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 Razor diretiva em uma página. @page afeta o comportamento de outras Razor construções. Razor Os nomes de arquivo de páginas têm um .cshtml sufixo.

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 Pages/Index2.cshtml.cs modelo de página:

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 PageModel arquivo de classe tem o mesmo nome que o Razor arquivo page com .cs acrescentado. Por exemplo, a página anterior Razor é Pages/Index2.cshtml. O arquivo que contém a PageModel classe é nomeado Pages/Index2.cshtml.cs.

As associações de caminhos de URL para páginas são determinadas pelo local da página no sistema de arquivos. A tabela a seguir mostra um Razor caminho de página 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 Razor arquivos de Páginas na pasta Páginas por padrão.
  • Index é a página padrão quando uma URL não inclui uma página.

Escrever um formulário básico

Razor As páginas foram projetadas para tornar os padrões comuns usados com navegadores da Web fáceis de implementar ao criar um aplicativo. Os auxiliares de model binding, Tag Helpers e HTML apenas funcionam com as propriedades definidas em uma Razor classe 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 Microsoft.EntityFrameworkCore.InMemory pacote NuGet.

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 Pages/Create.cshtml arquivo de exibição:

@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 Pages/Create.cshtml.cs modelo de página:

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 OnGet método exibe a CreateModel.cshtmlRazor Página.
  • OnPost para manipular envios de formulário.

O sufixo de nomenclatura Async é opcional, mas geralmente é usado por convenção para funções assíncronas. O código anterior é típico para Razor Páginas.

Se você estiver familiarizado com ASP.NET aplicativos usando controladores e exibições:

  • O OnPostAsync código no exemplo anterior é semelhante ao código típico do controlador.
  • A maioria dos primitivos do MVC, como associação de modelo, validação e resultados de ação, funcionam da mesma forma com Controladores e Razor Páginas.

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 Pages/Create.cshtml arquivo de exibição:

@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 HTML renderizado 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 OnPostAsync método manipulador chama o RedirectToPage método auxiliar. 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 páginas. Na amostra anterior, ele redireciona para a página de Índice raiz (/Index). RedirectToPage é detalhado na seção geração de URL para Páginas .
  • Com erros de validação que são passados para o servidor:

    • O OnPostAsync método manipulador chama o Page método auxiliar. 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 resulta em ModelState.IsValid retornando false. Neste exemplo, nenhum erro de validação é exibido no cliente. A entrega de erros de validação será abordada 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 Customer propriedade usa [BindProperty] o atributo para aceitar a associação de modelo:

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 contenham propriedades que não devem ser alteradas pelo cliente. Para obter mais informações, consulte Overposting.

Razor As páginas, por padrão, associam propriedades somente a verbos nãoGET verbos. 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 GET associação é útil ao abordar cenários que dependem de valores de cadeia de caracteres de consulta ou de rota.

Para associar uma propriedade em GET solicitações, defina a [BindProperty] propriedade do SupportsGet atributo como true:

[BindProperty(SupportsGet = true)]

Para obter mais informações, consulte ASP.NET Core Community Standup: Associar na discussão GET (YouTube).

Examinando o Pages/Create.cshtml arquivo de exibição:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>
  • No código anterior, o auxiliar<input asp-for="Customer.Name" /> de marca de entrada associa o elemento HTML <input> à expressão de Customer.Name modelo.
  • @addTagHelper disponibiliza auxiliares de marca.

Home page

Index.cshtml é a home page:

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customer)
            {
                <tr>
                    <td> @contact.Id  </td>
                    <td>@contact.Name</td>
                    <td>
                        <!-- <snippet_Edit> -->
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <!-- </snippet_Edit> -->
                        <!-- <snippet_Delete> -->
                        <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                        <!-- </snippet_Delete> -->
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

A classe associada PageModel (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 Index.cshtml arquivo contém a seguinte marcação:

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

O <a /a>Auxiliar de Marca de Âncora usou o asp-route-{value} atributo 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 permitem que o código do lado do servidor participe na criação e renderização de elementos HTML em Razor arquivos.

O Index.cshtml arquivo contém marcação para criar um botão de exclusão para cada contato do 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 excluir é renderizado em HTML, sua formação inclui parâmetros para:

  • A ID de contato do cliente, especificada pelo asp-route-id atributo.
  • O handler, especificado pelo asp-page-handler atributo.

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 a 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 "{id:int}" de roteamento informa à página para aceitar solicitações para a página que contêm int dados de rota. 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 System.ComponentModel.DataAnnotations namespace fornece um conjunto de atributos de validação internos que são aplicados declarativamente 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 Customer modelo:

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 Create.cshtml arquivo de exibição:

@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 scripts de validação jQuery e jQuery.

  • Usa os <div />auxiliares de marca e <span /> de marca 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". Se o JavaScript estiver habilitado no cliente, o navegador exibirá o erro sem postar no servidor.

O [StringLength(10)] atributo é gerado data-val-length-max="10" no HTML renderizado. data-val-length-max impede que os 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 um comprimento máximo de 10".

Considere o seguinte Movie modelo:

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 aplicado às propriedades do modelo às quais são aplicados:

  • Os Required atributos e os MinimumLength atributos indicam que uma propriedade deve ter um valor, mas nada impede que um usuário insira espaço em branco para atender a essa validação.

  • O atributo RegularExpression é usado para limitar quais caracteres podem ser inseridos. No código anterior, "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 e números especiais em 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 StringLength atributo define o comprimento máximo de uma propriedade de cadeia de caracteres e, opcionalmente, seu comprimento 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 Movie modelo mostra erros com valores inválidos:

Movie view form with multiple jQuery client-side validation errors

Para obter mais informações, consulte:

Manipular solicitações HEAD com um fallback de manipulador OnGet

HEAD solicitações que 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!");
}

Razor As páginas voltarão a chamar o OnGet manipulador se nenhum OnHead manipulador for definido.

XSRF/CSRF e Razor páginas

Razor As páginas são protegidas pela validação antiforgeria. O FormTagHelper injeta tokens antiforgery em elementos de formulário HTML.

Usando layouts, parciais, modelos e auxiliares de marca com Razor páginas

As páginas funcionam com todos os recursos do mecanismo de exibição Razor . Layouts, parciais, modelos, Auxiliares _ViewStart.cshtmlde Marca e _ViewImports.cshtml funcionam da mesma maneira que funcionam para exibições convencionais Razor .

Organizaremos essa página aproveitando alguns desses recursos.

Adicionar uma página de layout a 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 Razor página é renderizado onde @RenderBody() é chamado.

Para obter mais informações, consulte a página de layout.

A propriedade Layout está 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 Páginas/Compartilhado pode ser usado de qualquer Razor página na pasta Páginas .

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. Razor As páginas são destinadas a depender da hierarquia de pastas, não de convenções de caminho.

Exibir a pesquisa de uma Razor página inclui a pasta Páginas . Os layouts, modelos e parciais usados com controladores MVC e exibições convencionais Razorapenas funcionam.

Adicione um Pages/_ViewImports.cshtml arquivo:

@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 @namespace diretiva definida em uma página:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

A @namespace diretiva define o namespace da página. A diretiva @model não precisa incluir o namespace.

Quando a @namespace diretiva está contida _ViewImports.cshtml, o namespace especificado fornece o prefixo para o namespace gerado na Página que importa a @namespace diretiva. O restante do namespace gerado (a parte do sufixo) é o caminho relativo separado por ponto entre a pasta que contém _ViewImports.cshtml e a pasta que contém a página.

Por exemplo, a PageModel classe Pages/Customers/Edit.cshtml.cs define explicitamente o namespace:

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

O Pages/_ViewImports.cshtml arquivo define o seguinte namespace:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

O namespace gerado para a Pages/Customers/Edit.cshtmlRazor Página é o mesmo que a PageModel classe.

@namespacetambém funciona com exibições convencionais 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 atualizado Pages/Create.cshtml com _ViewImports.cshtml 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, os _ViewImports.cshtml auxiliares de marca e namespace importados. O arquivo de layout importou os arquivos JavaScript.

O Razor projeto inicial do Pages contém a Pages/_ValidationScriptsPartial.cshtmlvalidação do lado do cliente.

Para obter mais informações sobre exibições parciais, consulte exibições parciais em 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

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

As Pages/Customers/Create.cshtml páginas e Pages/Customers/Edit.cshtml o redirecionamento após o Pages/Customers/Index.cshtml sucesso. 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 Pages/Customers/Index.cshtml página. Por exemplo:

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

O nome /Index absoluto da página é usado para gerar URLs para a Pages/Index.cshtml página. 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 de geração de URL anteriores oferecem opções avançadas e funcionalidades funcionais ao 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 index é selecionada usando parâmetros diferentes RedirectToPage em Pages/Customers/Create.cshtml.

RedirectToPage(x) Página
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index"); Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index

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 entre páginas em uma pasta:

  • Renomear uma pasta não interrompe os links relativos.
  • Os links não estão quebrados 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 as ASP.NET Core e Razor as convenções de aplicativo e de rotas de páginas no ASP.NET Core.

Atributo ViewData

Os dados podem ser passados para uma página com ViewDataAttribute. As propriedades com o [ViewData] atributo têm seus valores armazenados e carregados do ViewDataDictionary.

No exemplo a seguir, o AboutModel atributo é aplicado [ViewData] à Title propriedade:

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

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, quando 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 Pages/Customers/Index.cshtml arquivo exibe o valor de Message uso TempData.

<h3>Msg: @Model.Message</h3>

O Pages/Customers/Index.cshtml.cs modelo de página aplica o [TempData] atributo à Message propriedade.

[TempData]
public string Message { get; set; }

Para obter mais informações, consulte 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 gostar da cadeia de caracteres ?handler=JoinList de consulta na URL, altere a rota para colocar o nome do manipulador na parte de caminho da URL. A rota pode ser personalizada adicionando um modelo de rota entre aspas duplas após a @page diretiva.

@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 configurações avançadas

A configuração e as configurações nas seções a seguir não são exigidas pela maioria dos aplicativos.

Para configurar opções avançadas, use a AddRazorPages sobrecarga que configura RazorPagesOptions:

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
    {
        options.RootDirectory = "/MyPages";
        options.Conventions.AuthorizeFolder("/MyPages/Admin");
    });
}

Use o RazorPagesOptions diretório raiz para páginas ou adicione convenções de modelo de aplicativo para páginas. Para obter mais informações sobre convenções, consulte Razor as convenções de autorização do Pages.

Para pré-compilar exibições, consulte Razor a compilação de exibição.

Especifique que Razor as páginas estão na raiz do conteúdo

Por padrão, Razor as páginas têm raiz no diretório /Pages . Adicione WithRazorPagesAtContentRoot para especificar que suas Razor Páginas estão na raiz de conteúdo (ContentRootPath) do aplicativo:

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesAtContentRoot();
}

Especifique que Razor as páginas estão em um diretório raiz personalizado

Adicione WithRazorPagesRoot para especificar que Razor as Páginas 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