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 Pages | HTML с сервера, 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 и модели. |
| PRG | Post-Redirect-Get: после POST редирект на GET, чтобы F5 не дублировал отправку. |
| CSRF | Подделка запроса с чужого сайта; формы защищают скрытым токеном. |
Что получится
| URL | Файл | Действие |
|---|---|---|
/ | Pages/Index.cshtml | Приветствие |
/Notes | Pages/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 /notesASP.NET Core связывает этот маршрут с текущим PageModel и вызываетOnGet. - Такой стиль удобен, когда URL нужно сделать человеко-понятным и независимым от структуры папок.
Без @page файл — просто фрагмент; с @page — маршрут. По умолчанию путь строится из папки: Pages/Notes/Index.cshtml → /Notes.
Структура:
Pages/
Notes/
Index.cshtml ← разметка
Index.cshtml.cs ← OnGet, OnPost, свойства
Разбор:
- Диаграмма описывает полный цикл
GETиPOSTдля одной страницы/Notes. - На
GETPageModel получает данные из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(). |
Конструктор NoteStore | DI подставляет зарегистрированный сервис. |
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)] | Ограничить размер данных |
ModelState | if (!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 |
Что попробовать
OnPostDelete(int id)с кнопкой удаления в форме.- Замените
NoteStoreна EF Core. [Authorize]на папкеNotesпосле Identity — JWT, cookie и MVC.
Дальше
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.
В подборках
Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:
Первые шаги (маршрут подборки) — .NET MAUI — первая программа, CMake — первая программа, Blazor — первая программа, Qt — первая программа, Первая программа на ASP.NET Core, Qt Quick — первая программа на QML.