Forum — веб-приложение форума с backend на ASP.NET Core Web API и простым клиентским интерфейсом.
Приложение позволяет пользователям регистрироваться и авторизовываться, работать со своим профилем, публиковать записи, просматривать ленту и загружать аватар. Backend предоставляет REST API, а демонстрационный web-интерфейс на HTML, CSS и JavaScript обслуживается непосредственно ASP.NET Core приложением.
Проект разработан как практический пример построения backend-приложения с разделением ответственности между слоями, JWT-аутентификацией, PostgreSQL, Redis, валидацией запросов, централизованной обработкой ошибок, структурированным логированием и контейнеризацией.
На текущий момент реализованы:
- регистрация пользователей;
- авторизация с использованием JWT;
- получение информации о текущем пользователе;
- получение и изменение профиля;
- создание публикаций;
- получение публикации по идентификатору;
- получение ленты публикаций с cursor-based пагинацией;
- получение публикаций конкретного пользователя;
- загрузка пользовательского аватара;
- преобразование загружаемых изображений в WebP;
- выдача и HTTP-кэширование пользовательских аватаров;
- Redis-кэширование;
- ограничение частоты запросов для чувствительных операций;
- централизованная обработка исключений;
- автоматическое применение EF Core migrations в режиме
Development; - health check приложения и базы данных;
- Swagger/OpenAPI-документация;
- структурированное логирование;
- интеграция с OpenTelemetry;
- запуск всего окружения через Docker Compose;
- простой web-интерфейс для взаимодействия с приложением.
- C#
- .NET 9
- ASP.NET Core Web API
- Entity Framework Core
- ASP.NET Core Identity
- JWT Bearer Authentication
- MediatR
- FluentValidation
- AutoMapper
- PostgreSQL 18
- Npgsql
- Redis 8
- HTML
- CSS
- JavaScript
- ASP.NET Core Static Files
Web-интерфейс предназначен прежде всего для демонстрации возможностей API и позволяет взаимодействовать с основными функциями приложения без отдельного frontend-проекта.
- Docker
- Docker Compose
- Serilog
- OpenTelemetry
- Swagger / OpenAPI
- ASP.NET Core Rate Limiting
- ASP.NET Core Health Checks
- ImageSharp
Приложение разделено на четыре основных слоя:
src/
├── Domain/
├── Application/
├── Infrastructure/
│ └── Migrations/
├── SocialNetworkAPI/
│ ├── Controllers/
│ ├── Extensions/
│ ├── Middleware/
│ ├── Services/
│ ├── wwwroot/
│ ├── Dockerfile
│ └── Program.cs
├── docker-compose.yml
└── SocialNetworkAPI.sln
Зависимости между слоями организованы таким образом, чтобы прикладная и доменная логика не зависели от конкретных инфраструктурных деталей.
Содержит доменные сущности и основные модели предметной области.
Слой не зависит от HTTP, базы данных, Redis и других инфраструктурных механизмов.
Содержит прикладную логику и сценарии использования системы:
- команды;
- запросы;
- DTO;
- MediatR handlers;
- валидацию;
- абстракции;
- pipeline behaviors;
- прикладные исключения.
Для разделения операций чтения и изменения данных используются элементы подхода CQRS.
Содержит реализации инфраструктурных компонентов:
- доступ к PostgreSQL через Entity Framework Core;
- EF Core migrations;
- ASP.NET Core Identity;
- JWT;
- Redis;
- репозитории;
- хранение пользовательских файлов;
- работу с кэшированием;
- логирование;
- конфигурацию инфраструктурных сервисов.
Представляет входную точку приложения и отвечает за HTTP-уровень:
- REST API;
- контроллеры;
- middleware;
- authentication и authorization;
- Swagger;
- rate limiting;
- health checks;
- CORS;
- статические файлы;
- конфигурацию HTTP pipeline.
Большинство операций приложения доступны через REST API.
Защищенные endpoint'ы требуют JWT Bearer token:
Authorization: Bearer <token>Регистрация пользователя:
POST /api/auth/registerАвторизация:
POST /api/auth/loginПри успешной авторизации API возвращает JWT, который используется для обращения к защищенным ресурсам.
Для endpoint авторизации дополнительно настроен rate limiting.
Получение данных текущего пользователя:
GET /api/users/meПолучение профиля:
GET /api/users/me/profileИзменение профиля:
PUT /api/usersВсе перечисленные операции требуют авторизации.
Получение ленты:
GET /api/postsПоддерживаются параметры cursor-based пагинации:
cursorCreatedAt
cursorId
take
Получение конкретной публикации:
GET /api/posts/{id}Создание публикации:
POST /api/postsПолучение публикаций определенного пользователя:
GET /api/users/{userId}/postsДля получения общей ленты используется cursor-based pagination по дате создания и идентификатору публикации.
На уровне PostgreSQL для этого создан составной индекс:
(CreationDate, Id)
Это позволяет эффективнее получать следующие страницы ленты без использования обычной offset-пагинации.
Загрузка или изменение аватара:
POST /api/users/me/avatarEndpoint принимает изображение через multipart/form-data.
Для загрузки настроены:
- ограничение размера HTTP-запроса;
- ограничение допустимого размера изображения;
- проверка MIME-типа;
- rate limiting;
- обработка изображения;
- сохранение результата в формате WebP.
Получение сохраненного аватара:
GET /api/files/avatars/{userId}/{avatarId}Endpoint получения изображения является публичным.
Для HTTP-кэширования используются:
ETag;If-None-Match;Last-Modified;Cache-Control;- ответ
304 Not Modified.
В приложении используются несколько механизмов защиты.
Аутентификация реализована с помощью:
- ASP.NET Core Identity;
- JWT Bearer Authentication.
Защищенные endpoint'ы требуют действительный JWT.
Используются отдельные политики для различных типов запросов.
Для авторизации применяется Fixed Window Rate Limiter.
Для загрузки файлов используется Token Bucket Rate Limiter.
Это ограничивает количество чувствительных операций, которые клиент может выполнить за короткий промежуток времени.
При загрузке и выдаче пользовательских изображений предусмотрены:
- ограничение размера запроса;
- проверка входных данных;
- проверка MIME-типа;
- защита от path traversal;
- выдача фиксированного типа содержимого;
- HTTP-кэширование.
Для API используется отдельная CORS-политика.
Разрешенные origins задаются через конфигурацию приложения и могут быть переопределены через переменные окружения.
Для основной базы данных используется PostgreSQL.
Доступ к данным осуществляется через Entity Framework Core и провайдер Npgsql.
Схема базы данных управляется с помощью EF Core migrations.
В режиме:
Development
приложение при запуске автоматически выполняет:
Database.MigrateAsync()Поэтому при запуске через Docker Compose существующие миграции автоматически применяются к базе данных.
Отдельно выполнять:
dotnet ef database updateдля обычного Development-запуска не требуется.
Автоматическое применение migrations при старте используется здесь для упрощения локальной разработки. Для production-окружения предпочтительнее применять миграции как отдельный этап развертывания.
После изменения EF Core-модели необходимо создать новую migration.
Из корня репозитория:
dotnet ef migrations add MigrationName \
--project src/Infrastructure/Infrastructure.csproj \
--startup-project src/SocialNetworkAPI/SocialNetworkAPI.csprojПеред применением рекомендуется проверить содержимое созданной migration.
Проверить, существуют ли изменения модели, для которых еще не создана migration:
dotnet ef migrations has-pending-model-changes \
--project src/Infrastructure/Infrastructure.csproj \
--startup-project src/SocialNetworkAPI/SocialNetworkAPI.csprojПри отсутствии несохраненных изменений EF Core сообщит:
No changes have been made to the model since the last migration.
Redis используется в качестве внешнего in-memory хранилища для кэширования.
Подключение осуществляется через StackExchange.Redis.
В Docker Compose Redis запускается отдельным контейнером и доступен API внутри Docker-сети по адресу:
redis:6379
Для Redis используется persistent volume, поэтому данные могут сохраняться между перезапусками контейнера.
Приложение поддерживает загрузку пользовательских аватаров.
Для обработки изображений используется ImageSharp.
После проверки загруженное изображение обрабатывается и сохраняется в формате:
WebP
Файлы хранятся отдельно от основной базы данных.
При запуске через Docker Compose каталог пользовательских изображений подключен к отдельному Docker volume, поэтому загруженные аватары не теряются при пересоздании контейнера API.
Для структурированного логирования используется Serilog.
Логи выводятся:
- в консоль;
- в файлы.
При Docker-запуске файловые логи сохраняются в отдельный volume.
Проект содержит интеграцию с OpenTelemetry, позволяющую расширить наблюдаемость приложения и подключить внешние системы сбора telemetry и distributed tracing.
Состояние приложения можно проверить через:
GET /healthHealth check также проверяет доступность SocialNetworkDbContext.
При запуске окружения Docker Compose PostgreSQL и Redis имеют собственные health checks.
API запускается после того, как необходимые инфраструктурные сервисы становятся готовы к работе.
В режиме Development доступен Swagger UI.
При Docker-запуске:
http://localhost:8080/swagger
Swagger позволяет:
- просматривать доступные endpoint'ы;
- изучать параметры запросов;
- видеть возможные HTTP-ответы;
- отправлять запросы к API непосредственно из браузера.
Помимо REST API, приложение содержит небольшой клиентский интерфейс.
Он находится в:
src/SocialNetworkAPI/wwwroot/
В частности, реализованы страницы:
index.html
login.html
register.html
profile.html
ASP.NET Core обслуживает эти файлы через Static Files middleware.
При Docker-запуске главная страница доступна по адресу:
http://localhost:8080
Web UI является демонстрационным клиентом проекта и использует REST API приложения.
Проект можно запустить двумя способами:
- через Docker Compose — рекомендуемый и самый простой вариант;
- локально через .NET CLI.
Docker Compose запускает все необходимые компоненты приложения:
┌─────────────────────┐
│ API │
│ ASP.NET Core/.NET 9 │
│ :8080 │
└─────────┬───────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ PostgreSQL │ │ Redis │
│ 18 │ │ 8 │
│ :5432 │ │ :6379 │
└─────────────────┘ └─────────────────┘
Для этого варианта требуется:
- Docker
- Docker Compose
Устанавливать .NET SDK, PostgreSQL и Redis непосредственно в систему для запуска приложения не требуется.
git clone https://github.com/RomanGleyzer/Forum.git
cd ForumСоздайте файл:
src/.env
Например:
POSTGRES_DB=socialnet
POSTGRES_USER=postgres
POSTGRES_PASSWORD=change_this_password
JWT_KEY=replace_this_with_a_long_random_secret_key_at_least_32_bytes
CORS_ALLOWED_ORIGIN=http://localhost:8080Не рекомендуется добавлять настоящий .env с паролями и секретами в Git.
JWT-ключ должен содержать не менее 32 байт.
cd srcdocker compose up --buildDocker Compose:
- создаст необходимые volumes;
- запустит PostgreSQL;
- запустит Redis;
- дождется прохождения health checks;
- соберет Docker-образ API;
- запустит ASP.NET Core приложение;
- применит существующие EF Core migrations в режиме
Development.
После успешного запуска доступны:
| Компонент | Адрес |
|---|---|
| Web UI | http://localhost:8080 |
| REST API | http://localhost:8080/api/... |
| Swagger | http://localhost:8080/swagger |
| Health Check | http://localhost:8080/health |
| PostgreSQL | localhost:5431 |
| Redis | localhost:6379 |
PostgreSQL внутри Docker-сети работает на стандартном порту 5432, а для подключения с хост-машины опубликован порт 5431.
docker compose up --build -ddocker compose psВсе сервисы:
docker compose logsТолько API:
docker compose logs apiНаблюдение за логами API в реальном времени:
docker compose logs -f apidocker compose downПри обычном docker compose down persistent volumes сохраняются.
Чтобы полностью удалить контейнеры вместе с данными PostgreSQL, Redis, пользовательскими файлами и логами:
docker compose down -vКоманда с
-vудаляет persistent volumes и предназначена только для случаев, когда сохраненные данные больше не нужны.
Этот вариант удобен во время разработки, когда API запускается непосредственно через dotnet run.
Потребуются:
- .NET 9 SDK;
- доступный PostgreSQL;
- доступный Redis.
PostgreSQL и Redis при желании можно продолжать запускать через Docker Compose.
Из каталога src:
docker compose up -d postgres redisВ этом случае:
PostgreSQL → localhost:5431
Redis → localhost:6379
cd SocialNetworkAPIЕсли команда выполняется из корня репозитория:
cd src/SocialNetworkAPIДля локальной разработки секретные параметры рекомендуется хранить через .NET User Secrets:
dotnet user-secrets set \
"ConnectionStrings:PostgreSQLConnection" \
"Host=localhost;Port=5431;Database=socialnet;Username=postgres;Password=YOUR_PASSWORD"Если PostgreSQL установлен непосредственно на компьютере и работает на стандартном порту, вместо 5431 обычно будет использоваться 5432.
dotnet user-secrets set \
"ConnectionStrings:Redis" \
"localhost:6379"dotnet user-secrets set \
"Jwt:Key" \
"YOUR_LONG_SECRET_KEY_AT_LEAST_32_BYTES"dotnet restoredotnet runASP.NET Core выведет адреса приложения в консоль после запуска.
В режиме Development существующие EF Core migrations будут автоматически применены к базе данных.
API собирается с помощью многоэтапного Dockerfile на базе официальных образов .NET 9.
Сборка разделена на несколько этапов:
restore
↓
build
↓
publish
↓
ASP.NET Core Runtime
Build context включает все проекты решения:
Domain
Application
Infrastructure
SocialNetworkAPI
Это позволяет корректно восстанавливать и собирать межпроектные зависимости внутри Docker.
В Docker Compose используются отдельные persistent volumes:
postgres_data
redis_data
media_data
logs_data
Они отвечают соответственно за:
- данные PostgreSQL;
- данные Redis;
- пользовательские изображения;
- файловые логи приложения.
Roman Gleyzer
GitHub: RomanGleyzer