A modern .NET 10 full-stack platform built with Vertical Slice Architecture (VSA), multi-tenancy, and Blazor WebAssembly — designed for efficient AI-assisted feature development.
Try the live demo at https://krafter.getkrafter.dev/
Default Credentials:
- Email:
admin@getkrafter.dev - Password:
123Pa$$word!
Alternatively, log in with Google to create a new account.
First-time setup:
- Install tools (once)
- dotnet-ef:
dotnet tool install --global dotnet-ef - Kiota CLI (optional for client regen):
dotnet tool install --global Microsoft.Kiota.Cli
- Run Aspire orchestration (starts PostgreSQL, dynamic ports)
dotnet run --project aspire/AditiKraft.Krafter.Aspire.AppHost/AditiKraft.Krafter.Aspire.AppHost.csproj
Database migrations (first run or when schema changes):
- Copy DB connection string from Aspire Dashboard
- Open Aspire Dashboard (usually https://localhost:17285)
- In the Resources tab, expand the postgres resource (click the arrow > if collapsed)
- Find the krafterDb row (nested/indented under postgres)
- Click the three-dot menu (⋮) in the Actions column of the krafterDb row
- Select "View details" from the dropdown menu
- The details panel opens on the right showing "PostgresDatabaseResource: krafterDb"
- Scroll down in the details panel to find the Connection string field
- Click the eye icon (👁️) next to the connection string value to copy it
- Format:
Host=localhost;Port=56178;Username=postgres;Password=postgres;Database=krafterDb - Note: The port number changes with each Aspire run (e.g., 56178 in one run, 52961 in another)
-
Stop
krafter-apiservice in Aspire (unlocks Backend assembly) -
Set migration connection string
- EITHER update
src/AditiKraft.Krafter.Backend/appsettings.Local.json→ConnectionStrings:KrafterDbMigration - OR set env var:
ConnectionStrings__KrafterDbMigration="Host=localhost;Port=XXXXX;Username=postgres;Password=postgres;Database=krafterDb"
- Apply migrations (once)
cd src/AditiKraft.Krafter.Backenddotnet ef database update --context KrafterContextdotnet ef database update --context TenantDbContextdotnet ef database update --context BackgroundJobsContext
- Restart
krafter-apiin Aspire and open URLs
- Aspire Dashboard: https://localhost:17285
- Backend API: https://localhost:5199
- Swagger UI: https://localhost:5199/swagger
- Blazor UI: https://localhost:7291
Note: Ports are dynamic per run; use the Aspire Dashboard values.
- TL;DR Quick Start
- Overview
- Architecture
- Key Features
- Technology Stack
- Getting Started
- Project Structure
- Development Guide
- Deployment
- Contributing
- License
Krafter is a production-ready, enterprise-grade full-stack platform built with .NET 10, combining modern architectural patterns with cutting-edge technologies. It provides a solid foundation for building scalable, multi-tenant SaaS applications with rich user interfaces.
- 🏗️ Vertical Slice Architecture (VSA) - Backend organized by features, not layers
- 🌐 Hybrid Blazor - WebAssembly + Server rendering for optimal performance
- 🏢 Multi-Tenancy - Complete tenant isolation at the database level
- 🔐 Permission-Based Security - Fine-grained authorization with JWT
- ⚡ Real-Time Updates - SignalR integration for live notifications
- 📊 Observability - OpenTelemetry with Aspire orchestration
- 🎨 Modern UI - Radzen components with theming support
- 🔌 Refit API Client - Type-safe HTTP client with automatic token handling
- Vertical Slice Architecture (VSA) - Features organized by business capability
- Clean Code - Single Responsibility, DRY, SOLID principles
- Auto-Registration - Handlers, services, and routes discovered via markers
- Response Pattern - Consistent
Response<T>wrapper for all operations
- JWT Authentication - Secure token-based authentication
- Google OAuth - External authentication integration
- Permission-Based Authorization - Fine-grained access control
- Multi-Tenancy - Complete tenant isolation at DB level
- Token Refresh - Automatic token rotation
- Blazor Hybrid - WebAssembly + Server rendering
- Radzen Components - 70+ professional UI components
- Theme Support - Light/Dark/Auto modes with WCAG compliance
- Responsive Design - Mobile and desktop optimized
- Code-Behind Pattern - Clean separation of markup and logic
- Kiota Client - Type-safe, auto-generated API client
- EF Core - PostgreSQL & MySQL support
- Multi-Database - Separate contexts for tenants, jobs, and main data
- Migrations - Code-first database schema management
- Soft Delete - Recoverable data deletion
- Background Jobs - TickerQ for async processing
- SignalR - Real-time bi-directional communication
- Redis Cache - Distributed caching support
- Pagination - Efficient data loading
- Debouncing - Optimized search and filtering
- .NET Aspire - Orchestration and service discovery
- OpenTelemetry - Distributed tracing and metrics
- Structured Logging - Comprehensive application logs
- Health Checks - Service health monitoring
- NUKE Build - Automated build pipeline
- Docker Support - Containerized deployment
- GitHub Actions - CI/CD automation
- Auto Deployment - Webhook-triggered updates
- .NET 10 - Latest .NET framework
- ASP.NET Core - Minimal APIs
- Entity Framework Core 10 - ORM
- ASP.NET Core Identity - User management
- FluentValidation - Input validation
- TickerQ - Background job processing
- SignalR - Real-time communication
- Blazor WebAssembly - Client-side SPA
- Blazor Server - Server-side rendering
- Radzen Blazor - UI component library
- Refit - Type-safe REST API client
- Blazored LocalStorage - Browser storage
- FluentValidation.Blazor - Client-side validation
- Mapster - Object mapping
- .NET Aspire - Cloud-native orchestration
- OpenTelemetry - Observability
- Redis - Caching (optional)
- PostgreSQL / MySQL - Database
- Docker - Containerization
- NUKE - Build automation
- .NET 10 SDK
- Docker Desktop (for Aspire/PostgreSQL)
- Visual Studio 2022 17.11+ or VS Code
- EF Core tools:
dotnet tool install --global dotnet-ef
-
Clone the repository
git clone https://github.com/AditiKraft/Krafter.git cd Krafter -
Restore packages
dotnet restore
-
Run with Aspire (starts DB and services)
dotnet run --project aspire/AditiKraft.Krafter.Aspire.AppHost/AditiKraft.Krafter.Aspire.AppHost.csproj
-
Apply database migrations (quick path)
- Open Aspire Dashboard → copy PostgreSQL connection string
- Stop
krafter-apiservice - Set
ConnectionStrings__KrafterDbMigration(or updatesrc/AditiKraft.Krafter.Backend/appsettings.Local.json) - Run:
cd src/AditiKraft.Krafter.Backend dotnet ef database update --context KrafterContext dotnet ef database update --context TenantDbContext dotnet ef database update --context BackgroundJobsContext - Restart
krafter-api
-
Configure secrets (see Secrets and Configuration)
-
Access the application
- Aspire Dashboard: https://localhost:17285
- Backend API: https://localhost:5199
- Swagger UI: https://localhost:5199/swagger
- Blazor UI: https://localhost:7291
-
Default Credentials
On first run, the application seeds a default admin account:
- Email:
admin@getkrafter.dev - Password:
123Pa$$word!
Alternatively, log in with Google, which will create a new account.
⚠️ Important: Change the default password immediately in production! - Email:
Before running the application for the first time, you need to set up the database and apply migrations.
- ✅ Aspire orchestration must be running (provides PostgreSQL database)
- ✅ Backend API must be stopped before running migration commands
- ✅ Connection string configured in
appsettings.Local.jsonor via environment variable
dotnet run --project aspire/AditiKraft.Krafter.Aspire.AppHost/AditiKraft.Krafter.Aspire.AppHost.csprojThis starts PostgreSQL and generates a dynamic connection string.
Step-by-Step Visual Guide:
-
Open the Aspire Dashboard
- After starting Aspire orchestration, the console will display the dashboard URL (typically
https://localhost:17285) - Open this URL in your browser
- After starting Aspire orchestration, the console will display the dashboard URL (typically
-
Navigate to Resources
- The dashboard opens to the Resources view by default
- You'll see a table listing all running resources with columns: Name, State, Start time, Source, URLs, and Actions
-
Locate the PostgreSQL Resource
- Look for the resource named postgres in the resources list (it has a database icon)
- The State should show a green dot with "Running"
- Notice that postgres has a collapse/expand arrow (>) next to it
-
Expand the postgres Resource
- If not already expanded, click the arrow (>) next to postgres to expand it
- You'll see nested/indented child items appear: krafterDb, postgresPassword, and postgresUsername
-
Open krafterDb Details
- Locate the krafterDb row (the indented database resource under postgres)
- In the Actions column (far right) of the krafterDb row, click the three-dot menu button (⋮)
- A dropdown menu will appear with options: "View details", "Console logs", "Ask GitHub Copilot"
- Click on "View details"
-
View the Details Panel
- A details panel opens on the right side of the screen
- The panel header will display "PostgresDatabaseResource: krafterDb"
- This confirms you've opened the correct resource details
-
Find the Connection String
- In the right details panel, you'll see sections like: Name, State, Health state, Start time, etc.
- Scroll down to find the Connection string field (under the "Resource" section)
- The connection string value will be displayed (it may be partially masked)
-
Copy the Connection String
- Next to the connection string value, you'll see two icons:
- 📋 Copy icon - Click this to copy the connection string to your clipboard
- 👁️ Visibility toggle - Shows/hides the full connection string
- Click the copy icon to copy the complete connection string
- Next to the connection string value, you'll see two icons:
- In the Aspire dashboard, stop the
krafter-apiservice - Why? The Backend assembly is locked when the API is running, preventing EF Core migration tools from accessing it
After:
Important: Replace `Port=52961` with the port from your Aspire dashboard (it changes on each run).
Tip: You can also set the environment variable `ConnectionStrings__KrafterDbMigration` instead of modifying the file.
##### 5. Apply Migrations
Navigate to the Backend project directory and apply migrations:
```bash
cd src/AditiKraft.Krafter.Backend
# Apply all migrations
dotnet ef database update --context KrafterContext
dotnet ef database update --context TenantDbContext
dotnet ef database update --context BackgroundJobsContext
- In the Aspire dashboard, restart the
krafter-apiservice - Or restart the entire Aspire orchestration
Your database is now ready! 🎉
| Requirement | Reason |
|---|---|
| Aspire Running | Database connection strings are dynamically assigned by Aspire (ports change on each run) |
| Backend Stopped | EF Core migration tools compile and load the Backend assembly. If the API is running, the assembly is locked |
| appsettings.Local.json | With Backend stopped, we configure the connection string in appsettings or environment variables |
When you add new features that require database changes:
cd src/AditiKraft.Krafter.Backend
# Create a new migration
dotnet ef migrations add <MigrationName> --context KrafterContext
# Apply the migration
dotnet ef database update --context KrafterContext| Issue | Cause | Solution |
|---|---|---|
| "Unable to create DbContext" | Backend API is running | Stop the Backend API in Aspire dashboard |
| "Database connection failed" | Wrong port or Aspire not running | Verify Aspire is running and copy the correct connection string from dashboard |
| "Could not find DbContext" | Wrong working directory | Ensure you're in src/AditiKraft.Krafter.Backend directory |
| "Migration already exists" | Duplicate migration name | Use dotnet ef migrations remove --context <ContextName> |
dotnet-ef not found |
EF tools not installed | Run dotnet tool install --global dotnet-ef |
Before running the application, configure the following secrets and settings:
aspire/AditiKraft.Krafter.Aspire.AppHost/appsettings.json
Parameters:postgresUsername={YOUR_POSTGRES_USERNAME}Parameters:postgresPassword={YOUR_POSTGRES_PASSWORD}
src/AditiKraft.Krafter.Backend/appsettings.json
TickerQBasicAuth:Username={TICKERQ_BASIC_AUTH_USERNAME}TickerQBasicAuth:Password={TICKERQ_BASIC_AUTH_PASSWORD}SecuritySettings:JwtSettings:Key={JWT_SIGNING_KEY}
src/UI/AditiKraft.Krafter.UI.Web/appsettings.Development.json
Jwt:Key={JWT_SIGNING_KEY}(MUST be identical to backend JWT key)
src/UI/AditiKraft.Krafter.UI.Web.Client/wwwroot/appsettings.json
RemoteHostUrl={YOUR_BACKEND_HOST}Authentication:Google:ClientId={YOUR_GOOGLE_CLIENT_ID}(optional, for Google OAuth)
⚠️ Important: The JWT signing key must be exactly the same for the Backend and the UI server host. This ensures tokens issued by the backend validate correctly during server-side rendering and prerendered Blazor scenarios.
From the Backend project (src/AditiKraft.Krafter.Backend):
# initialize once (if needed)
dotnet user-secrets init
# set JWT and TickerQ basic auth
dotnet user-secrets set "SecuritySettings:JwtSettings:Key" "<long-random-32+ chars>"
dotnet user-secrets set "TickerQBasicAuth:Username" "<username>"
dotnet user-secrets set "TickerQBasicAuth:Password" "<password>"From the Blazor Server host (src/UI/AditiKraft.Krafter.UI.Web):
# initialize once (if needed)
dotnet user-secrets init
# JWT key must match Backend
dotnet user-secrets set "Jwt:Key" "<same-long-random-key-as-backend>"Krafter/
├── Agents.md # AI agent instructions (entry point)
├── aspire/ # Aspire orchestration
│ ├── Krafter.Aspire.AppHost/ # Orchestration host
│ └── Krafter.Aspire.ServiceDefaults/ # Shared configuration
├── src/
│ ├── Krafter.Shared/ # Shared contracts library
│ │ ├── Agents.md # Shared-specific AI instructions
│ │ ├── Contracts/ # API DTOs (Auth, Users, Roles, Tenants)
│ │ ├── Common/ # Shared utilities, permissions, models
│ │ └── Hubs/ # SignalR hub contracts
│ ├── Backend/ # ASP.NET Core API (VSA)
│ │ ├── Agents.md # Backend-specific AI instructions
│ │ ├── Features/ # Vertical slices (Auth, Users, Roles, Tenants)
│ │ ├── Infrastructure/ # Persistence, Background Jobs
│ │ ├── Application/ # Auth, Multi-tenancy, Notifications
│ │ ├── Api/ # API configuration, middleware, authorization
│ │ ├── Entities/ # Base entity classes
│ │ ├── Hubs/ # SignalR hubs
│ │ └── Program.cs # Entry point
│ └── UI/
│ ├── Agents.md # UI-specific AI instructions
│ ├── Krafter.UI.Web.Client/ # Blazor WebAssembly
│ │ ├── Features/ # Feature-based UI components
│ │ ├── Infrastructure/ # Refit clients, Auth, Services
│ │ └── Common/ # Shared components, models
│ └── Krafter.UI.Web/ # Blazor Server host
├── tests/ # Test projects
├── build/ # NUKE build automation
├── docs/ # Documentation assets
├── .github/ # GitHub Actions workflows
└── README.md # This file
For detailed structure, see Agents.md and sub-project Agents.md files.
Backend (VSA Pattern):
- Create feature folder:
Features/<Feature>/ - Add operation files (e.g.,
Create<Feature>.cs,Get<Feature>s.cs) - Add entity to
Features/<Feature>/_Shared/<Entity>.cs - Update
KrafterContext.cswith newDbSet - Create EF configuration in
Infrastructure/Persistence/Configurations/ - Run migration:
dotnet ef migrations add Add<Feature> - Add permissions to
Common/Auth/Permissions/KrafterPermissions.cs
UI (Blazor):
- Create feature folder:
Features/<Feature>/ - Add list page:
<Feature>s.razor+<Feature>s.razor.cs - Add form dialog:
CreateOrUpdate<Feature>.razor+.razor.cs - Add route constant to
Common/Constants/KrafterRoute.cs - Create Refit interface:
Infrastructure/Refit/I<Feature>sApi.cs - Register Refit client in
Infrastructure/Refit/RefitServiceExtensions.cs - Update permissions in
Common/Permissions/KrafterPermissions.cs - Update
Infrastructure/Services/MenuService.csfor navigation
For complete guidelines, see Agents.md and the sub-project Agents.md files in src/AditiKraft.Krafter.Backend/, src/UI/, and src/AditiKraft.Krafter.Shared/.
# Build solution
dotnet build
# Run tests
dotnet test
# Create migration
dotnet ef migrations add <Name> --project src/AditiKraft.Krafter.Backend --context KrafterContext
dotnet ef migrations add <Name> --project src/AditiKraft.Krafter.Backend --context BackgroundJobsContext
dotnet ef migrations add <Name> --project src/AditiKraft.Krafter.Backend --context TenantDbContext
# Update database
dotnet ef database update --project src/AditiKraft.Krafter.Backend --context KrafterContext
dotnet ef database update --project src/AditiKraft.Krafter.Backend --context BackgroundJobsContext
dotnet ef database update --project src/AditiKraft.Krafter.Backend --context TenantDbContextBuild images:
dotnet publish src/AditiKraft.Krafter.Backend/AditiKraft.Krafter.Backend.csproj -c Release -p:PublishProfile=DefaultContainer
dotnet publish src/UI/AditiKraft.Krafter.UI.Web/AditiKraft.Krafter.UI.Web.csproj -c Release -p:PublishProfile=DefaultContainerThe project includes automated CI/CD pipelines that:
- Build and test on every push
- Create Docker images for
mainanddevbranches - Push images to GitHub Container Registry
- Trigger deployment webhooks
Troubleshooting: Can't Find the Connection String?
- ✅ Make sure you expanded the postgres resource (click the arrow >)
- ✅ Locate the krafterDb row indented under postgres
- ✅ Click the three-dot menu (⋮) in the Actions column of the krafterDb row (far right)
- ✅ Select "View details" from the dropdown menu (NOT "Console logs")
- ✅ The details panel should show "PostgresDatabaseResource: krafterDb" at the top
- ✅ If the connection string field is empty or shows a dash (-), wait a few seconds for the database to fully initialize
- ✅ Try refreshing the Aspire Dashboard page if the postgres resource doesn't appear
- ✅ Ensure Docker Desktop is running (Aspire uses Docker to host PostgreSQL)
See .github/workflows for configuration.
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Follow the coding conventions in copilot-instructions.md
- Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Use Conventional Commits:
feat(scope): add new feature
fix(scope): fix bug
docs(scope): update documentation
refactor(scope): refactor code
test(scope): add tests
This project is licensed under the MIT License - see the LICENSE file for details.
- .NET Team - For the amazing .NET platform
- Radzen - For the excellent Blazor components
- Refit - For the type-safe REST client
- NUKE Build - For the build automation framework
- Documentation: Agents.md (AI instructions and project structure)
- Issues: GitHub Issues
- Discussions: GitHub Discussions
You can use Krafter as a template to quickly scaffold new projects with a single command.
dotnet new install AditiKraft.Krafter.Templatesdotnet new krafter -n MyCompanyApp
cd MyCompanyApp
dotnet run --project aspire/MyCompanyApp.Aspire.AppHost/MyCompanyApp.Aspire.AppHost.csprojImportant: Always use the
-nparameter to specify your project name. This replaces "Krafter" with "MyCompanyApp" throughout the entire codebase (namespaces, project files, folder names, etc.).
dotnet new updatedotnet new uninstall AditiKraft.Krafter.TemplatesBuilt with ❤️ by Aditi Kraft
⭐ Star this repository if you find it helpful!