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

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

Разработчику

Play ITЗагрузка интерактивного демо…


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

Модель Razor Pages

Страница = разметка + C#-класс рядом (PageModel): верстка и обработчик в одном месте, проще проследить путь данных из формы. Для первых CRUD-экранов и внутренних панелей это часто быстрее, чем отдельный SPA.


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

Web API (Первая программа на ASP.NET Core) отдаёт 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 (в ASP.NET - фреймворк для веб-приложений)Крупный сайт: один контроллер на много разных View

Мы сделаем мини-сайт "Заметки": список и форма добавления. Данные в памяти — как в Первая программа на ASP.NET Core; с БД — Entity Framework Core — первая программа.


Словарь

ТерминПростыми словами
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

Разбор:

  • dotnet new webapp создаёт проект Razor Pages со стандартной структурой Pages, wwwroot, Program.cs.
  • Ключ -n NotesRazor задаёт имя проекта и будущую сборку NotesRazor.csproj.
  • Ключ -o NotesRazor указывает папку вывода, чтобы шаблон создал отдельный каталог.
  • cd NotesRazor переключает рабочую директорию в корень проекта перед запуском.
  • dotnet run собирает проект, поднимает встроенный Kestrel и выводит URL локального сервера.

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


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

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

@page "/notes"

Разбор:

  • Директива @page переводит .cshtml из шаблона-представления в полноценный endpoint Razor Pages.
  • Строка "/notes" явно фиксирует маршрут страницы и отключает автогенерацию URL из пути файла.
  • При GET /notes ASP.NET Core связывает этот маршрут с текущим PageModel и вызывает OnGet.
  • Такой стиль удобен, когда URL нужно сделать человеко-понятным и независимым от структуры папок.

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

Структура:

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

Разбор:

  • Диаграмма описывает полный цикл GET и POST для одной страницы /Notes.
  • На GET PageModel получает данные из NoteStore и сервер рендерит HTML со списком заметок.
  • На POST браузер отправляет форму, сервер выполняет Add(text) и сохраняет новую запись.
  • Ответ 302 Redirect реализует паттерн PRG и предотвращает повторную отправку формы при обновлении страницы.
  • Повторный GET /Notes уже читает обновлённые данные и отдаёт их как итоговое состояние UI.

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

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

Разбор:

  • namespace NotesRazor.Models; группирует модель в отдельном пространстве имён и упрощает импорт.
  • Класс Note задаёт структуру сущности, которую будут хранить и показывать Razor-страницы.
  • Свойство Id служит уникальным идентификатором записи для отображения и будущих операций обновления/удаления.
  • Text хранит содержимое заметки; инициализация "" исключает null в простом учебном примере.
  • Created заполняется DateTime.UtcNow, чтобы дата создавалась автоматически и была в универсальном формате UTC.

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

Код ITЗагрузка примера кода…

Разбор:

  • Поле _items хранит заметки в памяти процесса, без базы данных и внешних зависимостей.
  • _nextId обеспечивает последовательную выдачу идентификаторов при каждом добавлении.
  • Метод GetAll() сортирует список по Created в обратном порядке, поэтому новые заметки показываются первыми.
  • Возврат IReadOnlyList<Note> скрывает возможность прямого изменения коллекции из внешнего кода.
  • В Add(string text) вызывается Trim(), чтобы убрать лишние пробелы по краям пользовательского ввода.

Program.cs:

builder.Services.AddSingleton<NoteStore>();

Разбор:

  • builder.Services — контейнер зависимостей, где регистрируются сервисы приложения.
  • AddSingleton<NoteStore>() создаёт один экземпляр NoteStore на весь жизненный цикл процесса.
  • Все запросы и PageModel получают один и тот же список заметок, поэтому данные "общие" для текущего запуска.
  • Для учебного примера это самый короткий путь к рабочему состоянию без настройки БД.

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


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

Pages/Notes/Index.cshtml.cs:

Код ITЗагрузка примера кода…

Разбор:

  • Наследование PageModel даёт HTTP-обработчики OnGet/OnPost и доступ к инфраструктуре Razor Pages.
  • Конструктор принимает NoteStore, который подставляется через DI, и сохраняет его в _store.
  • Items служит моделью отображения списка заметок в .cshtml через Model.Items.
  • Атрибуты [BindProperty], [Required], [StringLength] связывают ввод формы и задают правила валидации.
  • В OnGet() страница заполняет Items для первичной отрисовки.
  • В OnPost() сначала проверяется ModelState, затем при успехе выполняется добавление и RedirectToPage() по схеме PRG.

Разбор 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:

Код ITЗагрузка примера кода…

Разбор:

  • @model NotesRazor.Pages.Notes.IndexModel связывает разметку с конкретным PageModel-классом.
  • Форма method="post" отправляет данные на тот же URL, из-за чего вызывается метод OnPost.
  • asp-for="NewText" генерирует корректные name/id/value и синхронизирует поле с NewText.
  • asp-validation-for и asp-validation-summary выводят ошибки валидации из ModelState.
  • Цикл @foreach проходит по Model.Items и рендерит каждую заметку вместе с локальным временем создания.
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

Разбор:

  • @using NotesRazor добавляет пространство имён по умолчанию для Razor-страниц.
  • @namespace NotesRazor.Pages задаёт базовый namespace для классов, связанных с .cshtml.
  • @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers включает стандартные tag helpers ASP.NET Core во всём разделе Pages.
  • Благодаря этой строке работают asp-for, asp-validation-for, asp-page и другие специальные атрибуты.

Валидация формы на практике

Частая схема для полей ввода:

ПолеПроверкаЗачем
NewText[Required]Не сохранять пустые записи
NewText[StringLength(500)]Ограничить размер данных
ModelStateif (!ModelState.IsValid)Вернуть форму с ошибками

Когда логика становится сложнее, валидацию стоит вынести в отдельные модели запроса и сервисы предметной области. Для API-варианта этого же потока см. Первая программа на ASP.NET Core и Identity — JWT, cookie и MVC.


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

Pages/Index.cshtml:

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

Разбор:

  • Тег <a href="/Notes"> создаёт переход на страницу заметок по абсолютному пути.
  • Абсолютный URL гарантирует корректный переход из любого раздела сайта.
  • Такой простой линк полезен как стартовая навигация между главной страницей и CRUD-экраном.
dotnet run

Разбор:

  • Команда запускает приложение в текущей конфигурации и собирает изменения перед стартом.
  • После запуска можно проверить, что ссылка /Notes открывается и форма работает.
  • Этот шаг закрепляет полный цикл — рендер, отправка POST, редирект и повторный GET.

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


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

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

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

Разбор:

  • AddRazorPages() подключает инфраструктуру серверного HTML и обработчиков PageModel.
  • AddControllers() включает pipeline для API-контроллеров и JSON endpoint'ов.
  • MapRazorPages() публикует маршруты страниц из папки Pages.
  • MapControllers() активирует маршрутизацию контроллеров ([ApiController], [Route]).
  • Комбинация показывает гибридный подход: UI на Razor и интеграционный слой на Web API в одном хосте.

Админка на Razor, публичное API — на контроллерах. Архитектура: ASP.NET - фреймворк для веб-приложений.


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

СимптомПричина
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 после Identity — JWT, cookie и MVC.

Дальше


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.


В подборках

Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:

Первые шаги (маршрут подборки) — .NET MAUI — первая программа, CMake — первая программа, Blazor — первая программа, Qt — первая программа, Первая программа на ASP.NET Core, Qt Quick — первая программа на QML.