Перейти к основному содержимому

Razor Pages — первая программа

Разработчику
Загрузка симулятора первой программы…

Razor Pages — первая программа

Razor Pages — серверный HTML

Web API (4511) отдаёт JSON — данные для программ. Razor Pages отдаёт готовый HTML: сервер собирает страницу из шаблона и отправляет браузеру. Удобно для админок, внутренних панелей, форм регистрации, отчётов — без отдельного React/Vue.

Модель простая: одна страница = два файла

  • Something.cshtml — разметка (HTML + директивы Razor);
  • Something.cshtml.cs — класс PageModel с методами OnGet, OnPost.
ПодходКогда выбирать
Web APIМобильное приложение, SPA, интеграции
Razor PagesHTML с сервера, CRUD-формы, отчёты
BlazorИнтерактивный UI на C# в браузере (SignalR или WASM)
MVC (в 451)Крупный сайт: один контроллер на много разных View

Мы сделаем мини-сайт «Заметки»: список и форма добавления. Данные в памяти — как в 4511; с БД — 441.


Словарь

ТерминПростыми словами
PageModelКласс с данными страницы и обработчиками HTTP.
OnGetВыполняется при GET — показать страницу.
OnPostВыполняется при отправке формы method="post".
Model bindingЗначения из формы попадают в свойства с [BindProperty].
ModelStateСписок ошибок валидации после привязки.
Tag HelperАтрибуты asp-for, asp-validation-for — связь HTML и модели.
PRGPost-Redirect-Get: после POST редирект на GET, чтобы F5 не дублировал отправку.
CSRFПодделка запроса с чужого сайта; формы защищают скрытым токеном.

Что получится

URLФайлДействие
/Pages/Index.cshtmlПриветствие
/NotesPages/Notes/IndexСписок + форма
POST /Notesтот же PageModelДобавить заметку

Требования

  • .NET SDK 8+
  • Базовый HTML помогает читать разметку, но не обязателен

Создание проекта

dotnet new webapp -n NotesRazor -o NotesRazor
cd NotesRazor
dotnet run

Шаблон webapp включает Razor Pages (папка Pages/). Откройте URL из консоли — стартовая страница уже работает.


Как устроена страница

Директива вверху .cshtml:

@page "/notes"

Без @page файл — просто фрагмент; с @pageмаршрут. По умолчанию путь строится из папки: Pages/Notes/Index.cshtml/Notes.

Структура:

Pages/
Notes/
Index.cshtml ← разметка
Index.cshtml.cs ← OnGet, OnPost, свойства

Модель заметки

Models/Note.cs:

namespace NotesRazor.Models;

public class Note
{
public int Id { get; set; }
public string Text { get; set; } = "";
public DateTime Created { get; set; } = DateTime.UtcNow;
}

Services/NoteStore.cs — хранилище в памяти:

using NotesRazor.Models;

namespace NotesRazor.Services;

public class NoteStore
{
private readonly List<Note> _items = new();
private int _nextId = 1;

public IReadOnlyList<Note> GetAll() =>
_items.OrderByDescending(n => n.Created).ToList();

public void Add(string text)
{
_items.Add(new Note { Id = _nextId++, Text = text.Trim() });
}
}

Program.cs:

builder.Services.AddSingleton<NoteStore>();

Singleton — один список на всё приложение (для учебного примера достаточно; с БД будет DbContext со Scoped).


PageModel — список и добавление

Pages/Notes/Index.cshtml.cs:

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using NotesRazor.Services;

namespace NotesRazor.Pages.Notes;

public class IndexModel : PageModel
{
private readonly NoteStore _store;

public IndexModel(NoteStore store) => _store = store;

public IReadOnlyList<Models.Note> Items { get; private set; } = [];

[BindProperty]
[Required(ErrorMessage = "Введите текст заметки")]
[StringLength(500)]
public string NewText { get; set; } = "";

public void OnGet()
{
Items = _store.GetAll();
}

public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
Items = _store.GetAll();
return Page();
}

_store.Add(NewText);
return RedirectToPage();
}
}

Разбор PageModel

ЭлементСмысл
: PageModelБазовый класс Razor Pages; даёт ModelState, RedirectToPage, Page().
Конструктор NoteStoreDI подставляет зарегистрированный сервис.
ItemsДанные для списка в разметке (Model.Items).
[BindProperty]При POST поле формы с именем NewText заполнит свойство.
[Required], [StringLength]Валидация; ошибки попадут в ModelState.
OnGet()Подготовить данные и показать страницу (без изменений).
!ModelState.IsValidОшибка валидации — снова показать форму с сообщениями.
return Page()Отрендерить тот же .cshtml с текущей моделью.
RedirectToPage()PRG: после успеха редирект на GET /Notes — обновление F5 только перечитает список.

Разметка страницы

Pages/Notes/Index.cshtml:

@page
@model NotesRazor.Pages.Notes.IndexModel

<h1>Заметки</h1>

<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<input asp-for="NewText" class="form-control" placeholder="Новая заметка" />
<span asp-validation-for="NewText" class="text-danger"></span>
<button type="submit" class="btn btn-primary mt-2">Добавить</button>
</form>

<ul class="mt-4">
@foreach (var note in Model.Items)
{
<li><small>@note.Created.ToLocalTime():g</small> — @note.Text</li>
}
</ul>
Tag HelperЧто делает
asp-for="NewText"name, id, значение поля связаны с NewText; для POST — правильное имя.
asp-validation-for="NewText"Показать текст ошибки из ModelState.
asp-validation-summary="All"Сводка всех ошибок над формой.
<form method="post">Отправка на тот же URL → вызов OnPost.

Форма с tag helpers добавляет anti-forgery token — скрытое поле, без которого POST вернёт 400 (защита от CSRF).

Pages/_ViewImports.cshtml (шаблон создаёт сам):

@using NotesRazor
@namespace NotesRazor.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Ссылка с главной

Pages/Index.cshtml:

<p><a href="/Notes">Мои заметки</a></p>
dotnet run

Добавьте заметки, нажмите F5 — дубликатов POST не будет благодаря redirect.


Razor Pages и Web API в одном проекте

builder.Services.AddRazorPages();
builder.Services.AddControllers();

var app = builder.Build();
app.MapRazorPages();
app.MapControllers();

Админка на Razor, публичное API — на контроллерах. Архитектура: 451.


Частые ошибки

СимптомПричина
404 на /NotesНет @page или файл не Pages/Notes/Index.cshtml
POST не сохраняетНет [BindProperty] или method="get" у формы
Двойная отправка при F5Нет RedirectToPage() после успешного POST
Валидация не виднаНет asp-validation-for / tag helpers
400 Bad Request на POSTУбрали tag helpers — нет anti-forgery token

Что попробовать

  1. OnPostDelete(int id) с кнопкой удаления в форме.
  2. Замените NoteStore на EF Core.
  3. [Authorize] на папке Notes после 4515.

Дальше


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).