Razor Pages no ASP.NET Core: Arquitetura e Implementação de Operações CRUD

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

Tags: aspnet-core razor-pages CSharp entity-framework-core ORM

Publicado em 10-22 10:30