Live demo: https://blog.unrealbg.com/
Welcome to the Blazor Blog Project! This repository hosts a modern, responsive blog application built with Blazor Web App on .NET 10. The goal is to deliver fast, interactive user interfaces with a clean architecture and a practical admin workflow.
- Blazor Web App (Server render mode)
- Responsive Design
- Interactive UI with QuickGrid for admin tables
- Identity (cookie auth) with seeded Admin user
- Rich text editor for posts with direct Quill integration
- Configurable EF Core migrations and data seeding on startup
- Serilog logging (console + rolling files)
- Health endpoints: GET /health and GET /ready
- Static asset versioning and cache headers for production
- In-memory caching for public lists (2 min TTL) with automatic cache bust on admin changes
- HTML sanitization for user content (Ganss.Xss)
- Server-side validation with FluentValidation
- PostgreSQL persistence through EF Core and Npgsql
The gallery below was captured in dark mode with representative demo content, so the public and admin workflows are easier to scan.
This solution follows Clean Architecture:
- Domain: Core entities and business rules with no dependencies.
- Application: Use cases, contracts, and validators; depends only on Domain.
- Infrastructure: EF Core persistence, ASP.NET Core Identity, and service implementations; depends on Application.
- Web (BlazorBlog): UI; depends on Application and Infrastructure.
Data and Identity live under BlazorBlog.Infrastructure.Persistence (single ApplicationDbContext and ApplicationUser). UI helpers use Application abstractions (e.g., IToastService) implemented in Infrastructure.
- BlazorBlog (UI)
- BlazorBlog.Infrastructure (EF Core, Identity, seeding, data services)
- BlazorBlog.Application (view models, validators, contracts)
- BlazorBlog.Domain (entities)
- BlazorBlog.AppHost (Aspire local orchestration)
- BlazorBlog.Tests (xUnit v3 + bUnit)
- .NET 10 / ASP.NET Core Blazor Web App
- EF Core 10 with Npgsql/PostgreSQL
- ASP.NET Core Identity with role-based authorization
- QuickGrid, FluentValidation, Mapster, Serilog
- Tailwind CSS
- Aspire AppHost for local orchestration
- xUnit v3, bUnit, Moq, coverlet
- .NET 10 SDK
- Docker Desktop or another OCI-compatible container runtime for Aspire/Docker workflows
- Recommended: Visual Studio 2022 (latest) with ASP.NET workload
- PostgreSQL. The default local connection string expects
postgres/postgresonlocalhost:5432 - Node.js 18+ (LTS) if you plan to run the Tailwind CSS watcher during development or rely on the publish-time CSS build
For local development, update the connection string and Admin user settings in BlazorBlog/appsettings.json or user secrets:
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=blazorblog;Username=postgres;Password=postgres"
},
"AdminUser": {
"Name": "Admin",
"Email": "admin@bblog.com",
"Password": "Admin@123",
"Role": "Admin"
}
}Production must override AdminUser:Password; the app refuses to start with the default Admin@123 password outside Development.
Optional runtime settings:
{
"Database": {
"ApplyMigrationsOnStartup": false,
"SeedOnStartup": true
},
"ForwardedHeaders": {
"KnownProxies": [ "10.0.0.10" ]
},
"HealthChecks": {
"MinimumFreeDiskBytes": 104857600
},
"Email": {
"Host": "smtp.example.com",
"Port": 587,
"EnableSsl": true,
"UserName": "smtp-user",
"Password": "smtp-password",
"SenderEmail": "no-reply@example.com",
"SenderName": "Blazor Blog",
"RequireConfiguredSender": true
}
}To start a local PostgreSQL container:
docker run --name blazorblog-postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=blazorblog -p 5432:5432 -d postgres:16The AppHost starts PostgreSQL, waits for it to become healthy, injects ConnectionStrings__DefaultConnection, and starts the Blazor app with a /health check.
dotnet run --project BlazorBlog.AppHost/BlazorBlog.AppHost.csprojThe Aspire dashboard opens at http://localhost:15053.
The AppHost pins PostgreSQL 16 and stores data in the blazorblog-postgres16-data Docker volume. If you previously ran the project with PostgreSQL 17, keep the old volume for backup or remove it after exporting any local data you still need.
From the repository root:
# Runs the UI project
dotnet run --project BlazorBlog/BlazorBlog.csprojdotnet restore BlazorBlog.sln
dotnet build BlazorBlog.sln- One-time setup (inside
BlazorBlog/):
npm ci- Watch and rebuild CSS during development (run in a separate terminal from
BlazorBlog/):
npm run dev:css- Build CSS once (e.g., CI/local without watcher):
npm run build:cssNotes:
- On publish, CSS is built automatically by an MSBuild target that runs
npx tailwindcss(requires Node.js installed on the machine). - The generated stylesheet is
BlazorBlog/wwwroot/app.css. - Local static assets are referenced through Blazor static asset versioning and served with long-lived cache headers outside Development.
- In Development, pending EF Core migrations are applied automatically on startup
- Outside Development, set
Database:ApplyMigrationsOnStartup=trueto opt in - Initial data is seeded via
ISeedService(Admin role/user + default categories) whenDatabase:SeedOnStartupis true
Optional: You can still apply migrations manually with
dotnet ef database update, but it's not required for local runs.
- Cookie authentication using ASP.NET Core Identity
- Login page:
/Account/Login - Default local Admin credentials (change before first production run):
- Email:
admin@bblog.com - Password:
Admin@123
- Email:
Admin-only pages (require the Admin role):
/admin/dashboard/admin/manage-subscribers/admin/manage-users/admin/create-user
Content management pages require Admin or Editor:
/admin/manage-blog-posts(+ create/edit pages)/admin/manage-categories
- Pages:
/Account/ForgotPassword/Account/ResetPassword?email=...&code=...
- Email sending uses
IEmailSender<ApplicationUser> - Configure the
Emailsection for SMTP delivery; without it, the development fallback logs a warning and does not send email - Development helper: In Development the Forgot Password page displays a 'Development only' section with the generated reset link and token for easy local testing
GET /healthreturns{ status, timeUtc }GET /readychecks database connectivity and disk space, then returns a JSON report
- Serilog configured via
appsettings.json - Console + rolling file logs in
Logs/log-*.txt
- Run tests from the repo root:
dotnet test BlazorBlog.slnThe Dockerfile builds the Blazor app with the .NET 10 SDK image and publishes a runtime image on ASP.NET Core 10:
docker build -t blazorblog .
docker run --rm -p 8080:8080 \
--add-host=host.docker.internal:host-gateway \
-e ConnectionStrings__DefaultConnection="Host=host.docker.internal;Port=5432;Database=blazorblog;Username=postgres;Password=postgres" \
-e AdminUser__Password="ChangeMe-2026!" \
-e Database__ApplyMigrationsOnStartup=true \
-e Security__UseHttpsRedirection=false \
blazorblogOn native Linux, --add-host=host.docker.internal:host-gateway maps host.docker.internal to the Docker host gateway. Docker Desktop usually provides this name automatically.
With Compose, create a .env file or export variables first:
POSTGRES_PASSWORD=change-this-db-password
ADMIN_USER_PASSWORD=ChangeMe-2026!Then run:
docker compose up --buildContributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/NewFeature) - Commit your Changes (
git commit -m "Add some NewFeature") - Push to the Branch (
git push origin feature/NewFeature) - Open a Pull Request
Distributed under the MIT License. See LICENSE.txt for more information.
Zhelyazko Zhelyazkov - admin@unrealbg.com







