Visão Geral do Modelo Razor Pages
A plataforma ASP.NET Core MVC introduziu o Razor Pages como uma abordagem unificada para construir aplicações web focadas em paginação. Diferente da estrutura MVC tradicional que separa estritamente controladores, views e modelos, o Razor Pages permite agrupar a lógica de página e a renderização HTML em um único arquivo ou conjunto de arquivos acoplados.
Essa arquitetura simplifica o desenvolvimento quando se lida com cenários de páginas específicas, onde o fluxo de controle é direto sem a necessidade de múltiplos controladores. O framework suporta diretamente a sintaxe Razor misturada com código C#, proporcionendo uma curva de aprendizado reduzida e uma implementação mais concisa.
Estrutura de Pastas e Arquivos
O projeto segue uma convenção de nomeamento específica para identificar os componentes da aplicação:
Pasta Pages
Todos os pontos de entrada das páginas são armazenados nesta pasta. Cada página é composta por dois arquivos distintos:
- .cshtml: Contém o markup HTML e o código Razor.
- .cshtml.cs: Armazena a classe de código por trás (Code-Behind), herdando geralmente de
PageModel, responsável pelos eventos da página como carregamento ou envio de formulário.
Arquivos auxiliares, como layouts compartilhados (_Layout.cshtml) ou partes comuns, são prefixados com underline (_).
Pasta wwwroot
Nesta diretório ficam os ativos estáticos acessíveis publicamente: CSS, JavaScript, imagens e outras mídias não processadas pelo motor de servidor.
Arquivos de Configuração
- appsettings.json: Define strings de conexão e variáveis de ambiente globais.
- Program.cs: Ponto de enicialização da aplicação host.
- Startup.cs: Configure serviços e middleware, incluindo a configuração do banco de dados e roteamento.
Inicialização e Roteamento
Para habilitar funcionalidades do Razor Pages, é necessário registrar os serviços de página na dependência injecção dentro do método ConfigureServices.
public void ConfigureServices(IServiceCollection services)
{
// Habilita suporte nativo ao Razor Pages
services.AddRazorPages();
}
Cada arquivo no namespace Pages requer a diretiva @page como primeira instrução. Isso sinaliza ao compilador que aquele arquivo deve ser tratado como um endpoint HTTP independente.
@page
@model PaginaInicialModel
<h1>Bem-vindo ao sistema</h1>
O mecanismo de roteamento mapeia o caminho físico dos arquivos para URLs baseadas em convenções:
/Pages/Index.cshtml→/ou/Index/Pages/Detalhes.cshtml→/Detalhes/Pages/Vendas/Cadastro.cshtml→/Vendas/Cadastro
Implementação Prática: Gerenciamento de Produtos
Abaixo, apresentamos um exemplo completo de uma aplicação que gerencia entidades usando banco de dados em memória para fins de demonstração.
Dependências Necessárias
Adicione o pacote Entity Framework Core para InMemory:
dotnet add package Microsoft.EntityFrameworkCore.InMemory
Definição de Modelos e Contexto
Criamos uma classe POCO representando o domínio e um contexto derivado de DbContext.
// Produto.cs
public class ItemProduto
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Nome { get; set; }
}
// AplicacaoDbContext.cs
public class AplicacaoDbContext : DbContext
{
public AplicacaoDbContext(DbContextOptions options) : base(options) {}
public DbSet<ItemProduto> Itens { get; set; }
}
Configuração da Aplicação
No Startup, registramos o serviço de contexto e as páginas de roteamento.
public void ConfigureServices(IServiceCollection services)
{
var connString = "NomeDoBanco";
services.AddDbContext<AplicacaoDbContext>(opts =>
opts.UseInMemoryDatabase(connString));
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapRazorPages());
}
Cadastro Novo Registro (Create)
A página Create.cshtml utiliza TagHelpers para vincular campos do formulário aos propriedades do modelo backend.
<!-- Create.cshtml -->
@page
@model CriarRegistroModel
<form method="post">
<input asp-for="Produto.Nome" />
<button type="submit">Salvar</button>
</form>
A lógica correspondente no Code-Behind utiliza model binding automático:
public class CriarRegistroModel : PageModel
{
private readonly AplicacaoDbContext _contexto;
public CriarRegistroModel(AplicacaoDbContext context)
{
_contexto = context;
}
[BindProperty]
public ItemProduto Produto { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) return Page();
await _contexto.Itens.AddAsync(Produto);
await _contexto.SaveChangesAsync();
return RedirectToPage("/Listagem");
}
}
Listagem de Dados (Read)
A visualização utiliza loop foreach para exibir os itens recuperados via EF Core com AsNoTracking para melhor performance na leitura apenas.
public async Task OnGetAsync()
{
ListaItens = await _contexto.Itens.AsNoTracking().ToListAsync();
}
No markup, criamos links diretos para edição e métodos de remoção usando atributos de rota.
<table>
<tr>
<th>ID</th>
<th>Nome</th>
<th>Ações</th>
</tr>
@foreach(var item in ListaItens)
{
<tr>
<td>@item.Id</td>
<td>@item.Nome</td>
<td>
<a asp-page="./Editar" asp-route-id="@item.Id">Editar</a>
<form method="post" asp-page-handler="Excluir">
<button type="submit" asp-route-id="@item.Id">Remover</button>
</form>
</td>
</tr>
}
</table>
Atualização (Update)
A página de edição aceita parâmetros na URL para filtrar o registro específico. Utilizamos restrições de tipo no route.
@page "{id:int}"
@model EditarRegistroModel
A recuperação ocorre via FindAsync e a persistência via Attach modificando o estado da entidade.
public async Task OnGetAsync(int id)
{
Produto = await _contexto.Itens.FindAsync(id);
if (Produto == null) return RedirectToPage("/Listagem");
}
Remoção (Delete)
O handler de deleção segue a convenção OnPostDeleteAsync. Ao submeter o form com handler especificado, o método correto é chamado.
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var registro = await _contexto.Itens.FindAsync(id);
if (registro != null)
{
_contexto.Itens.Remove(registro);
await _contexto.SaveChangesAsync();
}
return RedirectToPage();
}