Разработка приложений на ASP.NET Core: Web API, MVC и Razor

Курс охватывает создание современных веб-приложений на ASP.NET Core с использованием Web API, MVC и Razor Pages. Вы изучите архитектуру, маршрутизацию, работу с данными, безопасность и развертывание приложений в продакшн.

1. Основы ASP.NET Core: проект, middleware, DI и конфигурация

Основы ASP.NET Core: проект, middleware, DI и конфигурация

ASP.NET Core — это кроссплатформенный фреймворк для создания веб-приложений. В этом курсе мы будем строить Web API, MVC-приложения и страницы Razor, но в основе у них одни и те же фундаментальные механизмы:

  • структура проекта и точка входа
  • конвейер обработки запросов (middleware pipeline)
  • контейнер внедрения зависимостей (DI)
  • конфигурация приложения
  • Эта статья задаёт базу, на которую мы будем опираться во всех последующих темах.

    Что создаёт шаблон ASP.NET Core и как он запускается

    Современные версии ASP.NET Core используют minimal hosting model: в проекте обычно есть один главный файл Program.cs, в котором настраивается приложение.

    Типичный Program.cs выглядит так:

    Что здесь происходит:

  • WebApplication.CreateBuilder(args) создаёт объект builder и поднимает базовую инфраструктуру: DI-контейнер, конфигурацию, логирование, информацию об окружении.
  • builder.Services — коллекция сервисов, которые попадут в DI-контейнер.
  • builder.Build() собирает приложение.
  • app.Use... и app.Map... формируют конвейер обработки запросов.
  • app.Run() запускает веб-сервер и начинает принимать HTTP-запросы.
  • Официальные основы: ASP.NET Core fundamentals overview.

    Структура проекта: что важно понимать сразу

    Структура может немного отличаться по шаблонам (Web API, MVC), но ключевые элементы похожи:

  • Program.cs — точка входа и настройка приложения.
  • Properties/launchSettings.json — профили запуска (порт, переменные окружения для локального старта).
  • appsettings.json — базовые настройки.
  • appsettings.{Environment}.json — настройки для конкретного окружения.
  • Controllers — контроллеры (для Web API и MVC).
  • Views — представления (для MVC).
  • Pages — Razor Pages.
  • Важно: контроллеры, Razor Pages и MVC — это надстройки, которые «подключаются» через DI и middleware. Поэтому сначала мы разбираем фундамент.

    Hosting и окружения

    ASP.NET Core различает окружения (например, Development, Staging, Production). Окружение влияет на:

  • какие файлы конфигурации подхватятся
  • включены ли подробные ошибки
  • какие middleware стоит включать
  • Проверять окружение можно так:

    app.Environment — это сервис, доступный приложению из коробки.

    Middleware: конвейер обработки HTTP-запроса

    Middleware — это компонент, который получает HttpContext и либо:

  • обрабатывает запрос и передаёт управление дальше
  • завершает обработку и возвращает ответ
  • Запрос проходит через цепочку middleware в порядке регистрации.

    !Диаграмма показывает, как запрос проходит через middleware и как ответ возвращается обратно

    Базовые методы: Use, Run, Map

  • app.Use(...) добавляет middleware, которое обычно вызывает следующий компонент через next().
  • app.Run(...) добавляет терминальный middleware: после него цепочка не продолжается.
  • app.Map(...) создаёт ветвление конвейера по пути (например, отдельная ветка для /admin).
  • Пример простого middleware, добавляющего заголовок:

    Порядок middleware имеет значение

    Некоторые компоненты обязаны стоять в определённом порядке. Типичный пример для приложений с маршрутизацией и авторизацией:

    Причина проста:

  • маршрутизация должна определить, какой endpoint выбран
  • аутентификация должна установить пользователя
  • авторизация должна проверить права на выбранный endpoint
  • Официальная документация: ASP.NET Core middleware.

    Endpoint routing: MapControllers и MapGet

    В ASP.NET Core конечные точки (endpoints) регистрируются через Map....

  • app.MapControllers() подключает атрибутную маршрутизацию контроллеров.
  • app.MapGet(...), app.MapPost(...) и другие методы создают минимальные endpoint’ы.
  • Пример minimal endpoint:

    В следующих статьях мы будем подробно сравнивать Web API (контроллеры) и минимальные API, но фундамент (routing + middleware) общий.

    Dependency Injection: внедрение зависимостей в ASP.NET Core

    DI (Dependency Injection) — это способ передавать объекту его зависимости извне, вместо того чтобы создавать их внутри. ASP.NET Core использует встроенный DI-контейнер.

    Ключевые роли:

  • сервис — класс, который предоставляет функциональность (например, репозиторий, клиент внешнего API)
  • контракт — обычно интерфейс (например, IWeatherService)
  • реализация — конкретный класс (например, WeatherService)
  • !Иллюстрация показывает, как DI-контейнер создаёт объекты и внедряет их в контроллер

    Регистрация сервисов

    Сервисы регистрируются в builder.Services:

    Далее их можно получить через конструктор контроллера (или другого сервиса):

    Время жизни сервисов (lifetimes)

    ASP.NET Core поддерживает три основных времени жизни:

  • AddSingleton — один экземпляр на всё приложение (создаётся один раз и переиспользуется).
  • AddScoped — один экземпляр на HTTP-запрос.
  • AddTransient — новый экземпляр при каждом запросе к сервису.
  • Практические ориентиры:

  • Singleton подходит для неизменяемых, потокобезопасных компонентов (например, кэши, провайдеры времени, фабрики).
  • Scoped часто используется для компонентов, которые завязаны на запрос: бизнес-сервисы, Unit of Work, контексты работы.
  • Transient подходит для лёгких, не хранящих состояние объектов.
  • Официальная документация: Dependency injection in ASP.NET Core.

    Распространённые ошибки DI

  • Регистрация сервиса как Singleton, если он внутри использует Scoped (часто приводит к ошибкам времени жизни и непредсказуемому состоянию).
  • Хранение пользовательских данных запроса в singleton-сервисе.
  • Создание зависимостей вручную через new вместо регистрации в DI (ломает тестируемость и единый жизненный цикл).
  • Конфигурация: appsettings, переменные окружения, user secrets

    ASP.NET Core собирает конфигурацию из нескольких источников (providers). Обычно используются:

  • appsettings.json
  • appsettings.{Environment}.json
  • переменные окружения
  • User Secrets (удобно для локальной разработки)
  • аргументы командной строки
  • Важно: источники накладываются друг на друга. Если ключ встречается несколько раз, обычно побеждает источник, добавленный позже.

    !Диаграмма показывает приоритет источников конфигурации

    Официальная документация: Configuration in ASP.NET Core.

    Пример appsettings.json

    Чтение значения напрямую:

    Options pattern: типобезопасные настройки

    Чаще настройки привязывают к классу — так безопаснее и удобнее.

    Класс настроек:

    Регистрация:

    Использование через IOptions<T>:

    Официальная документация: Options pattern in ASP.NET Core.

    Где хранить секреты

    Для разработки используйте User Secrets, чтобы не коммитить ключи в репозиторий. Для продакшена — переменные окружения или менеджеры секретов инфраструктуры.

    Документация: Safe storage of app secrets in development in ASP.NET Core.

    Сводка: что вы должны уметь после этой статьи

  • Объяснить роль Program.cs, builder и app.
  • Понимать, что запрос проходит через middleware по порядку.
  • Различать Use, Run, Map и понимать, почему порядок важен.
  • Регистрировать сервисы в DI и выбирать время жизни (Singleton/Scoped/Transient).
  • Настраивать и читать конфигурацию из appsettings и использовать Options pattern.
  • В следующих материалах мы начнём применять эти основы на практике: построим первые Web API endpoints, подключим контроллеры, научимся возвращать корректные HTTP-ответы и подготовим структуру для расширяемого приложения.

    2. Web API: маршрутизация, контроллеры, валидация и документация

    Web API: маршрутизация, контроллеры, валидация и документация

    Web API в ASP.NET Core обычно строится вокруг контроллеров и маршрутизации, а качество API во многом определяется тем, насколько строго вы валидируете входные данные и насколько хорошо документированы endpoints.

    В предыдущей статье мы разобрали фундамент: Program.cs, middleware-конвейер, DI и конфигурацию. В этой теме мы применяем эти основы к разработке HTTP API:

  • как запрос попадает в нужный action (маршрутизация)
  • как устроены контроллеры и ответы API
  • как работает model binding и автоматическая валидация
  • как подключить OpenAPI/Swagger и улучшить документацию
  • Как Web API “собирается” из DI и middleware

    Минимальная конфигурация Web API включает:

  • регистрацию сервисов MVC/Web API в DI
  • добавление endpoint’ов контроллеров в конвейер
  • Пример базового Program.cs:

    AddControllers() добавляет необходимые сервисы (model binding, форматирование JSON, валидация, фильтры), а MapControllers() публикует endpoints, найденные по атрибутам маршрутизации.

    Документация: ASP.NET Core Web API.

    Контроллеры: что это и как они устроены

    Контроллер в Web API чаще всего наследуется от ControllerBase (в отличие от MVC, где используется Controller для представлений).

    Типичный контроллер:

    Ключевые элементы:

  • [ApiController] включает поведение, ориентированное на API: автоматическую проверку ModelState, удобные правила привязки параметров, ответы в формате ProblemDetails при ошибках.
  • [Route("api/[controller]")] задаёт шаблон URL. Токен [controller] заменится на имя контроллера без суффикса Controller.
  • [HttpGet] помечает action как обработчик GET.
  • Документация: ApiController attribute.

    Возврат результатов: IActionResult, ActionResult<T> и статус-коды

    В Web API важно возвращать корректные HTTP-коды:

  • 200 OK для успешного чтения
  • 201 Created для успешного создания ресурса
  • 400 Bad Request при ошибках входных данных
  • 404 Not Found когда ресурс не найден
  • Практичный стиль сигнатур:

  • ActionResult<T> когда обычно возвращаете T, но иногда нужны ошибки (NotFound(), BadRequest())
  • IActionResult когда результаты могут быть разных типов
  • Пример:

    Маршрутизация: как запрос находит нужный action

    Маршрутизация в контроллерах почти всегда делается через атрибуты.

    !Диаграмма показывает путь запроса от URL до выбранного action и обратно к ответу

    Шаблоны маршрутов

    Примеры распространённых шаблонов:

  • [Route("api/todos")] фиксированный путь
  • [HttpGet("{id}")] параметр в URL
  • [HttpGet("{id:int}")] параметр с ограничением типа
  • Пример контроллера с несколькими маршрутами:

    Ограничения маршрута помогают избежать неоднозначностей и ошибок выбора endpoint.

    Документация: Routing to controller actions.

    Параметры: route, query, header, body

    ASP.NET Core использует model binding для сборки параметров action из разных частей запроса.

    Основные источники:

  • маршрут: /api/todos/5
  • query string: /api/todos?done=true&page=2
  • заголовки: X-Request-Id: ...
  • тело запроса: JSON для POST/PUT/PATCH
  • Атрибуты явной привязки делают код понятнее и снимают неясности:

    Для входных моделей (DTO) в POST/PUT обычно используется тело запроса:

    Документация: Model binding in ASP.NET Core.

    Валидация: как не принимать “плохие” данные

    В API критично валидировать входные DTO до выполнения бизнес-логики.

    В ASP.NET Core есть два уровня, которые вы будете использовать чаще всего:

  • валидация модели (правила на свойства DTO)
  • валидация бизнес-правил (например, уникальность, существование связанных сущностей)
  • Автоматическая валидация с [ApiController]

    Если включён [ApiController], то при ошибках валидации ASP.NET Core автоматически вернёт 400 Bad Request и тело ошибки в формате ProblemDetails, даже если вы не проверяли ModelState вручную.

    Обычно это выглядит так:

  • клиент отправил JSON
  • model binding собрал DTO
  • валидаторы нашли ошибки
  • pipeline вернул 400 и список ошибок
  • Документация: Model validation in ASP.NET Core.

    Data Annotations: быстрые правила на DTO

    Пример DTO с аннотациями:

    Что это даёт:

  • [Required] запрещает отсутствие значения
  • [StringLength] ограничивает длину и позволяет задать минимум
  • Важно понимать, что это валидация входной модели. Проверки вроде “заголовок должен быть уникальным” обычно делаются в сервисе/репозитории.

    Пользовательская валидация

    Если стандартных атрибутов не хватает, есть несколько подходов:

  • реализовать IValidatableObject в DTO
  • написать собственный атрибут валидации
  • использовать сторонние библиотеки (например, FluentValidation) и подключить их в DI
  • В рамках курса достаточно уверенно владеть встроенной валидацией и понимать, где заканчивается валидация DTO и начинается бизнес-логика.

    Документация API: OpenAPI и Swagger UI

    Хорошая документация ускоряет разработку и снижает число ошибок интеграции.

    Чаще всего в ASP.NET Core используют связку:

  • генерация OpenAPI-спецификации
  • Swagger UI для просмотра и тестирования
  • Подключение Swagger (Swashbuckle)

    Один из самых распространённых пакетов для Swagger в ASP.NET Core: Swashbuckle.

    Документация: Swashbuckle.AspNetCore.

    Пример настройки:

    AddEndpointsApiExplorer() публикует метаданные endpoint’ов, а AddSwaggerGen() строит OpenAPI-документ.

    Улучшение Swagger-документации через атрибуты

    Полезные атрибуты для контроллеров:

  • [Produces("application/json")] указывает формат
  • [ProducesResponseType] описывает возможные статус-коды
  • Пример:

    Это улучшает читаемость документации и делает контракт API более явным.

    Документация: ASP.NET Core OpenAPI support.

    Сквозной пример: простой CRUD для Todo

    Ниже упрощённый пример, который связывает темы DI, маршрутизации, контроллеров, валидации и статус-кодов.

    Модель и репозиторий (in-memory)

    Регистрация в DI:

    Для учебного примера singleton допустим: хранилище в памяти одно на всё приложение. В реальных проектах репозитории с доступом к БД обычно Scoped.

    DTO с валидацией

    Контроллер

    Что здесь важно:

  • маршрут api/todos/{id:int} однозначно выбирается по ограничению int
  • POST Create использует валидацию DTO, и при ошибке автоматически вернётся 400 (из-за [ApiController])
  • CreatedAtAction возвращает 201 и заголовок Location на созданный ресурс
  • NoContent() корректен для операций без тела ответа
  • Сводка

  • Контроллеры Web API обычно наследуются от ControllerBase, а [ApiController] включает удобное API-поведение.
  • Атрибутная маршрутизация определяет, какой action выполнится, и поддерживает ограничения ({id:int}).
  • Model binding собирает параметры из route/query/header/body, а явные [From...] улучшают читаемость.
  • Валидация DTO через Data Annotations + [ApiController] даёт автоматический 400 с ProblemDetails.
  • Swagger/OpenAPI подключается через AddEndpointsApiExplorer() и AddSwaggerGen(), а [ProducesResponseType] делает контракт API яснее.
  • 3. MVC: контроллеры, представления, модели и HTML Helpers

    MVC: контроллеры, представления, модели и HTML Helpers

    ASP.NET Core MVC (Model-View-Controller) — это подход для создания серверных веб-приложений, где:

  • контроллер принимает HTTP-запрос и решает, что делать дальше
  • модель описывает данные и правила валидации
  • представление генерирует HTML-ответ на основе модели
  • В предыдущих темах мы разобрали фундамент ASP.NET Core (DI, middleware, конфигурация) и построили Web API с контроллерами, маршрутизацией, валидацией и Swagger. MVC использует те же базовые механизмы, но вместо JSON-ответов обычно возвращает HTML-страницы.

    Официальная документация: ASP.NET Core MVC overview.

    Как MVC встраивается в конвейер ASP.NET Core

    MVC подключается через DI и endpoint routing так же, как Web API.

    Типичная настройка для MVC в Program.cs:

    Что важно:

  • AddControllersWithViews() регистрирует MVC-сервисы, включая поддержку представлений.
  • UseStaticFiles() позволяет отдавать статические файлы (CSS, JS, изображения) из папки wwwroot.
  • MapControllerRoute(...) задаёт конвенциональную маршрутизацию (шаблон URL по умолчанию).
  • !Поток обработки запроса в MVC от URL до HTML-ответа

    Контроллеры и actions в MVC

    Контроллер в MVC обычно наследуется от Controller (а не ControllerBase, как в Web API), потому что Controller включает поддержку представлений.

    Пример:

    Action-метод чаще всего возвращает IActionResult. Типовые результаты:

  • View(model) возвращает HTML-страницу, отрендеренную из представления.
  • RedirectToAction(...) делает перенаправление на другой action.
  • NotFound() и BadRequest() возвращают соответствующие HTTP-коды.
  • Документация: Controller actions in ASP.NET Core.

    Dependency Injection в контроллерах MVC

    Как и в Web API, зависимости внедряются через конструктор.

    Это напрямую опирается на материал первой статьи про DI и времена жизни сервисов.

    Маршрутизация в MVC

    В MVC используются два основных подхода.

  • конвенциональная маршрутизация через MapControllerRoute(...)
  • атрибутная маршрутизация через [Route], [HttpGet], [HttpPost]
  • Конвенциональная маршрутизация

    Шаблон:

  • {controller} берётся из имени контроллера (без суффикса Controller)
  • {action} берётся из имени метода
  • {id?} необязательный параметр
  • Пример URL /Todos/Edit/5 обычно попадёт в TodosController.Edit(int id).

    Атрибутная маршрутизация

    Её часто используют для более явных и стабильных URL.

    Полезное правило: если приложение совмещает Web API и MVC, атрибутные маршруты помогают избежать конфликтов (например, api/... для API и отдельные пути для страниц).

    Документация: Routing in ASP.NET Core.

    Модели в MVC: доменные модели и ViewModel

    В MVC слово модель используется в нескольких смыслах. Практически важно различать:

  • доменная модель — сущности и бизнес-данные приложения
  • ViewModel — модель, подготовленная специально для конкретного экрана
  • ViewModel обычно проще и безопаснее для UI, потому что:

  • содержит только нужные полям страницы данные
  • удобно валидируется
  • снижает риск случайно принять лишние поля от пользователя
  • Model binding и валидация

    MVC, как и Web API, поддерживает model binding: значения параметров action собираются из query string, route и тела формы.

    Валидация обычно строится на Data Annotations.

    В MVC вы чаще проверяете ModelState.IsValid вручную, потому что поведение [ApiController] (авто-400) относится именно к Web API.

    Что здесь важно:

  • [ValidateAntiForgeryToken] защищает POST-формы от CSRF-атак.
  • При ошибках валидации мы возвращаем ту же страницу View(vm), чтобы показать сообщения об ошибках.
  • Документация: Model validation in ASP.NET Core MVC.

    Представления: Razor, типизация и передача данных

    Представление в MVC — это файл .cshtml, который рендерится Razor-движком.

    Документация: Views in ASP.NET Core MVC.

    Сильная типизация представления

    На практике почти всегда делают типизированные представления.

    Замечания:

  • Директива @model задаёт тип модели, доступной в представлении как Model.
  • В примере используется подход через HTML Helpers, чтобы не писать разметку напрямую.
  • Встроенного Html.SubmitButton(...) в MVC нет, это пример кастомного helper. На практике кнопку можно генерировать иначе, но сам приём показывает, что helpers можно расширять.
  • ViewData, ViewBag и TempData

    Иногда нужно передать данные, которые не являются основной моделью страницы:

  • ViewData словарь string -> object, полезен для простых значений
  • ViewBag динамическая обёртка над ViewData
  • TempData хранит данные между запросами, часто используется после редиректа
  • Практическое правило:

  • для основной информации страницы используйте типизированную модель
  • для одноразовых сообщений после редиректа используйте TempData
  • HTML Helpers: зачем они нужны и как ими пользоваться

    HTML Helpers — это методы, которые генерируют HTML и помогают связать форму и модель, не думая о правильных именах полей и правилах привязки.

    Основные преимущества:

  • корректные name и id для полей на основе выражений x => x.Property
  • автоматическая интеграция с ModelState и валидацией
  • удобство при рефакторинге: переименовали свойство в ViewModel — компилятор подскажет, где править
  • Документация: Working with Forms in ASP.NET Core MVC.

    Часто используемые helpers

    | Задача | HTML Helper | |---|---| | Ссылка на action | @Html.ActionLink(...) | | Начать форму | @using (Html.BeginForm(...)) { ... } | | Защита формы | @Html.AntiForgeryToken() | | Поле ввода | @Html.TextBoxFor(...), @Html.EditorFor(...) | | Подпись | @Html.LabelFor(...) | | Ошибка для поля | @Html.ValidationMessageFor(...) | | Ошибки формы | @Html.ValidationSummary(...) | | Отобразить значение | @Html.DisplayFor(...) |

    Почему TextBoxFor и ValidationMessageFor работают вместе

    Связка выглядит так:

  • Пользователь отправляет форму.
  • MVC пытается собрать CreateTodoVm из значений формы.
  • Валидация заполняет ModelState ошибками.
  • Вы возвращаете View(vm).
  • ValidationMessageFor(x => x.Title) читает ошибку для Title из ModelState и выводит сообщение.
  • Это один из ключевых механизмов MVC: серверная валидация отражается на UI без ручного “склеивания” строк.

    Сквозной пример: список и создание Todo в MVC

    Ниже минимальная связка, продолжающая пример репозитория из темы про Web API (тот же ITodoRepository, но другой UI).

    Контроллер

    Представление списка (идея)

    Чтобы не смешивать данные домена с UI-логикой, часто делают отдельную модель списка, но для учебного примера можно отдать список TodoItem напрямую.

    Представление создания (идея)

    Этот пример показывает главный принцип MVC: контроллер управляет потоком, модель описывает данные и валидацию, а представление отвечает за генерацию HTML.

    Сводка

  • MVC использует те же основы ASP.NET Core, что и Web API: DI, middleware и routing.
  • Контроллеры MVC обычно наследуются от Controller и часто возвращают View(...).
  • Конвенциональная маршрутизация задаётся через MapControllerRoute, а атрибутная — через [Route] и [HttpGet].
  • Для UI чаще применяют ViewModel, а не доменные сущности.
  • HTML Helpers помогают строить формы, связывать поля с моделью и выводить ошибки из ModelState.
  • Дальше по курсу логично сравнить MVC-представления с Razor Pages и увидеть, как в Razor Pages меняется организация кода, но остаются те же механизмы model binding и валидации.

    4. Razor Pages: страницы, формы, tag helpers и частичные представления

    Razor Pages: страницы, формы, tag helpers и частичные представления

    Razor Pages — это способ создавать серверные веб-страницы на ASP.NET Core, где единицей разработки является страница, а не контроллер. По сути, Razor Pages использует те же базовые механизмы, которые вы уже изучили в предыдущих темах:

  • middleware-конвейер и endpoint routing
  • DI и внедрение зависимостей
  • model binding и валидацию
  • Но структура кода становится ближе к тому, как вы мыслите про UI: одна страница = один файл разметки + один класс логики.

    Официальная документация: ASP.NET Core Razor Pages.

    Когда Razor Pages удобнее, чем MVC

    MVC хорошо подходит, когда вы мыслите ресурсами и экранами через контроллеры и actions. Razor Pages особенно удобен, когда приложение похоже на набор страниц с формами: настройки, профили, админка, личный кабинет.

    Ключевое отличие в организации:

    | Критерий | MVC | Razor Pages | |---|---|---| | Точка входа | action в контроллере | page handler в PageModel | | Структура | Controllers + Views | Pages (страницы) | | Маршруты | конвенции и атрибуты | директива @page и шаблон маршрута | | Модель для UI | часто ViewModel | свойства PageModel и отдельные модели |

    Важно: Razor Pages не отменяет MVC. Это другой способ организовать UI, который использует тот же стек ASP.NET Core.

    Подключение Razor Pages в приложении

    Как и Web API или MVC, Razor Pages подключается через DI и публикацию endpoint’ов.

    Program.cs для Razor Pages:

    Что здесь важно:

  • AddRazorPages() регистрирует сервисы Razor Pages.
  • MapRazorPages() публикует endpoints, которые соответствуют страницам в папке Pages.
  • UseStaticFiles() нужен почти всегда, чтобы страница могла подключать CSS и JS из wwwroot.
  • Документация: Routing in Razor Pages.

    !Поток: URL -> выбор страницы -> handler -> рендер -> HTML

    Анатомия Razor Page

    Обычно страница состоит из двух файлов:

  • SomePage.cshtml — шаблон Razor (разметка)
  • SomePage.cshtml.cs — класс PageModel (логика)
  • Пример структуры:

  • Pages/Todos/Index.cshtml
  • Pages/Todos/Index.cshtml.cs
  • Директива @page и маршрутизация

    Файл становится страницей (то есть endpoint’ом), когда в нём есть директива @page.

    Минимальный Pages/Index.cshtml:

    Маршрут по умолчанию выводится из пути файла. Например:

  • Pages/Index.cshtml соответствует /
  • Pages/Todos/Index.cshtml соответствует /Todos
  • Можно задать шаблон маршрута прямо в @page:

    Это означает, что страница ожидает параметр id в URL.

    PageModel: обработчики OnGet и OnPost

    Логика страницы живёт в классе, который наследуется от PageModel. Вы пишете методы-обработчики (handlers), которые соответствуют HTTP-методам.

  • OnGet(...) для GET
  • OnPost(...) для POST
  • Пример Pages/Todos/Create.cshtml.cs:

    Что здесь важно:

  • Page() возвращает текущую страницу (аналогично View() в MVC).
  • RedirectToPage(...) делает редирект на другую Razor Page.
  • ModelState.IsValid используется так же, как в MVC.
  • Документация: Introduction to Razor Pages in ASP.NET Core.

    Именованные обработчики

    Кроме OnPost() можно делать именованные обработчики, чтобы различать несколько действий на одной странице:

  • OnPostSave()
  • OnPostDelete()
  • Выбор обработчика обычно идёт через параметр handler (или через атрибуты в форме и кнопках).

    Формы и model binding в Razor Pages

    Razor Pages активно опирается на model binding: данные из формы, query string и маршрута автоматически попадают в параметры handler’ов и свойства PageModel.

    BindProperty и SupportsGet

  • [BindProperty] говорит фреймворку привязывать данные запроса к свойству.
  • По умолчанию привязка через [BindProperty] ориентирована на POST.
  • Если нужно получать значения и на GET (например, фильтры списка), используется SupportsGet = true.
  • Пример фильтра в списке:

    Анти-CSRF защита

    Для HTML-форм на серверном рендеринге важна защита от CSRF. В экосистеме ASP.NET Core это делается антифрод-токеном (antiforgery token).

    Практическое правило:

  • формы, которые делают POST, должны включать антифрод-токен
  • обработчики POST должны ожидать корректный токен
  • Документация: Antiforgery in ASP.NET Core.

    Tag Helpers: зачем нужны и как читать их в коде

    Tag Helpers — это механизм, который добавляет к элементам разметки «умные» атрибуты: генерацию URL, привязку к модели, интеграцию с валидацией.

    Официальная документация: Tag Helpers in ASP.NET Core.

    Самые важные tag helpers для Razor Pages

  • asp-page генерирует ссылку на Razor Page
  • asp-page-handler выбирает именованный обработчик
  • asp-route-* добавляет значения route/query параметров
  • asp-for привязывает поле ввода к свойству модели
  • asp-validation-for выводит ошибку валидации для конкретного поля
  • asp-validation-summary выводит ошибки формы
  • Чтобы соблюсти технические требования этой статьи, примеры ниже показывают элементы разметки в квадратных скобках вместо обычной записи. Смысл и атрибуты остаются теми же.

    Пример ссылки на страницу деталей:

    Пример формы, отправляющей данные в OnPost():

    Почему это полезно:

  • asp-for связывает имя поля с Input.Title, и при переименовании свойства вы быстрее найдёте места, которые нужно поправить.
  • asp-validation-for умеет показывать ошибки из ModelState без ручной склейки сообщений.
  • Частичные представления в Razor Pages

    Частичное представление (partial view) — это переиспользуемый фрагмент разметки. Оно не является endpoint’ом и не обрабатывает запросы напрямую, а помогает:

  • не дублировать одинаковые куски UI
  • вынести рендеринг элемента списка в отдельный файл
  • стандартизировать отображение ошибок, карточек, таблиц
  • Документация: Partial views in ASP.NET Core.

    Где хранить partial

    Типичные места:

  • Pages/Shared для общих partial’ов
  • рядом со страницей, если partial специфичен для одной области
  • Часто partial называют с подчёркивания: _TodoRow.cshtml.

    Подключение partial

    Через HTML Helper (в Razor это нормальная практика, и здесь не нужны элементы разметки):

    Или через tag helper (в этой статье показываем в квадратных скобках):

    Сквозной пример: Razor Pages для Todo с DI и валидацией

    Ниже связываем несколько идей из прошлых статей:

  • DI и внедрение репозитория (из темы про основы ASP.NET Core)
  • model binding и валидация
  • рендер списка через partial
  • Репозиторий (напоминание идеи)

    Вы уже видели ITodoRepository в теме про Web API и MVC. В Razor Pages он подключается так же.

    Регистрация в Program.cs:

    Страница списка Pages/Todos/Index.cshtml.cs

    Шаблон списка Pages/Todos/Index.cshtml

    Partial _TodoRow.cshtml

    Страница создания Pages/Todos/Create.cshtml.cs

    Шаблон создания Pages/Todos/Create.cshtml

    Ниже показана идея формы и валидации через tag helpers (в квадратных скобках):

    Сводка

  • Razor Pages подключается через AddRazorPages() и MapRazorPages().
  • Файл .cshtml становится endpoint’ом, если содержит @page.
  • Логика страницы живёт в PageModel, основные обработчики: OnGet и OnPost, плюс именованные варианты.
  • Model binding и валидация работают так же концептуально, как в MVC: данные приходят в модель, ошибки попадают в ModelState.
  • Tag Helpers упрощают привязку полей к модели, генерацию ссылок и показ ошибок валидации.
  • Частичные представления помогают переиспользовать фрагменты UI и уменьшают дублирование разметки.
  • 5. Данные, безопасность и деплой: EF Core, Identity, тестирование и CI/CD

    Данные, безопасность и деплой: EF Core, Identity, тестирование и CI/CD

    В предыдущих статьях курса мы научились строить приложение на ASP.NET Core в трёх стилях: Web API, MVC и Razor Pages. Но почти любое реальное приложение требует ещё четырёх вещей:

  • надёжный доступ к данным (обычно база данных)
  • безопасность (аутентификация и авторизация)
  • автоматизированное тестирование
  • воспроизводимый деплой через CI/CD
  • В этой статье мы свяжем всё в единую картину: как EF Core встраивается в DI и конфигурацию, как подключить ASP.NET Core Identity, как тестировать приложение на уровне HTTP, и как собрать типовой конвейер CI/CD.

    !Общая карта: как данные и безопасность встраиваются в приложение

    EF Core: доступ к данным через DbContext

    Entity Framework Core (EF Core) — ORM для .NET, которая позволяет работать с базой данных через типизированные классы и запросы LINQ.

    Полезные ссылки:

  • Документация EF Core
  • Начало работы с EF Core
  • Почему EF Core хорошо сочетается с тем, что мы уже изучили

  • DI: DbContext регистрируется как сервис, обычно со временем жизни scoped (один на HTTP-запрос)
  • конфигурация: строка подключения живёт в appsettings.json и окружениях
  • middleware и endpoints: контроллеры, MVC и Razor Pages получают сервисы через конструктор
  • Подключение EF Core к приложению

    1) Установите провайдер EF Core, например для SQL Server:

    2) Добавьте строку подключения в appsettings.json:

    3) Создайте DbContext и сущности:

    4) Зарегистрируйте контекст в DI в Program.cs:

    Миграции EF Core: как схема базы становится управляемой

    Миграции позволяют:

  • версионировать изменения схемы
  • воспроизводимо обновлять базу на разных окружениях
  • Команды:

    Ссылки:

  • Миграции EF Core
  • Паттерн репозитория: когда нужен и когда не нужен

    Если приложение простое, можно использовать AppDbContext прямо в контроллере или PageModel. Если логика растёт, часто удобнее выделить слой сервисов.

    Пример сервиса, который использует EF Core:

    Регистрация сервиса:

    Ключевая мысль: scoped хорошо подходит и для DbContext, и для сервисов, которые его используют.

    Безопасность: аутентификация и авторизация

    Аутентификация отвечает на вопрос: кто ты?

    Авторизация отвечает на вопрос: что тебе можно?

    Ссылки:

  • Аутентификация в ASP.NET Core
  • Авторизация в ASP.NET Core
  • ASP.NET Core Identity: пользователи, пароли, роли

    ASP.NET Core Identity — готовая подсистема для:

  • регистрации и входа
  • хеширования паролей
  • восстановления доступа
  • ролей и claims
  • Ссылки:

  • Обзор ASP.NET Core Identity
  • #### Подключение Identity с EF Core

    1) Пакеты для Identity + EF Core (пример для SQL Server):

    2) Контекст Identity. Часто делают один контекст на всё приложение:

    3) Регистрация Identity в Program.cs:

    Важно: UseAuthentication() должен идти до UseAuthorization().

    Cookies или JWT: что выбрать

    Типовой выбор зависит от вида клиента.

    | Сценарий | Часто выбирают | Почему | |---|---|---| | MVC или Razor Pages (браузер, серверный рендеринг) | cookie-аутентификацию | хорошо сочетается с редиректами и анти-CSRF | | Web API для мобильного приложения или SPA | JWT bearer tokens | токен можно передавать в Authorization: Bearer ... |

    Документация JWT bearer:

  • JWT bearer authentication
  • Авторизация: роли, policies и атрибуты

    Минимальный уровень — требовать, чтобы пользователь был аутентифицирован:

    Для более гибких правил используют policies:

    И применение:

    Практические меры безопасности, которые нельзя забывать

  • включайте HTTPS
  • храните секреты вне репозитория
  • используйте анти-CSRF в формах
  • ограничивайте CORS только нужными источниками
  • Ссылки:

  • Управление секретами (user secrets)
  • Анти-CSRF (antiforgery)
  • CORS в ASP.NET Core
  • Тестирование: что и как тестировать в ASP.NET Core

    Тесты в веб-приложениях удобно делить на два уровня.

  • модульные тесты (unit): проверяют бизнес-логику сервисов без HTTP
  • интеграционные тесты: поднимают приложение в памяти и проверяют HTTP endpoints
  • Ссылки:

  • Интеграционное тестирование в ASP.NET Core
  • Модульные тесты сервисов

    Идея: сервисы должны быть тестируемыми, потому что зависимости приходят через DI. Например, можно подменять репозиторий или DbContext.

    Минимальные команды для создания тестового проекта:

    Интеграционные тесты через WebApplicationFactory

    WebApplicationFactory<TEntryPoint> запускает ваше приложение в памяти и даёт HttpClient.

    Пакет:

    Пример теста для Web API:

    Важно: чтобы такой тест работал, endpoint /health должен быть реально опубликован, например через app.MapGet("/health", ...).

    Тестовая база данных: что выбрать

    Варианты:

  • SQLite in-memory: часто удобнее, чем InMemory-провайдер, потому что ближе к настоящей SQL-базе
  • контейнеры в тестах: ближе всего к продакшену, но тяжелее в настройке
  • Документация по провайдерам EF Core:

  • Провайдеры EF Core
  • CI/CD: сборка, тесты и деплой

    CI/CD превращает ручной процесс в повторяемый конвейер.

  • CI (continuous integration): собрать проект и прогнать тесты на каждом пуше
  • CD (continuous delivery или deployment): собрать артефакт и задеплоить
  • !Как изменения проходят путь от репозитория до продакшена

    Минимальный пайплайн: build и test

    Если вы делаете только один шаг сегодня, сделайте этот: автоматический dotnet test.

    Команды локально:

    Пример GitHub Actions для .NET

    Документация:

  • GitHub Actions: сборка и тестирование .NET
  • Пример workflow .github/workflows/ci.yml:

    Публикация приложения

    Для деплоя обычно нужен publish-вывод:

    Дальше есть два частых пути:

  • деплой publish-папки на хостинг
  • сборка Docker-образа и деплой контейнера
  • Ссылки:

  • dotnet publish
  • Контейнеризация .NET приложений
  • Конфигурация для окружений и секреты в CI/CD

    Ключевые правила:

  • appsettings.json хранит дефолты
  • appsettings.Production.json хранит прод-настройки без секретов
  • секреты приходят из переменных окружения или secret-хранилища CI
  • Ссылка:

  • Конфигурация в ASP.NET Core
  • Как связать всё с Web API, MVC и Razor Pages

  • Web API: EF Core живёт в сервисах, контроллеры возвращают DTO и статус-коды, [Authorize] защищает endpoints
  • MVC: EF Core используется в контроллерах или сервисах, Identity часто работает через cookie-аутентификацию, формы защищаются анти-CSRF
  • Razor Pages: тот же DbContext и сервисы через DI, авторизация чаще всего на уровне страниц и областей, формы также защищены анти-CSRF
  • Сводка

  • EF Core подключается через AddDbContext, а миграции делают схему базы управляемой и воспроизводимой
  • Identity даёт готовую систему пользователей и ролей, а UseAuthentication() и UseAuthorization() включают её в pipeline
  • тестирование в ASP.NET Core удобно строить через модульные тесты сервисов и интеграционные тесты с WebApplicationFactory
  • CI/CD минимум должен включать dotnet build и dotnet test, а деплой обычно опирается на dotnet publish и окружения конфигурации