Learning Project — This project is built for learning purposes. Any developer is welcome to clone it, explore the code, experiment, and build on top of it.
A full-featured e-commerce backend built with .NET 10 and Clean Architecture. The solution exposes two separate REST APIs — one for admin management and one for customer-facing operations — orchestrated with .NET Aspire.
- Architecture Overview
- Project Structure
- Tech Stack
- Domain Model
- API Reference
- Authentication
- Request & Response Format
- Getting Started
- Running Tests
- Key Features
The solution follows Clean Architecture with a strict dependency rule — inner layers never depend on outer layers:
Domain ← Application ← Infrastructure ← API
| Layer | Responsibility |
|---|---|
| Domain | Entities, value objects, domain exceptions, repository interfaces |
| Application | Use cases, service interfaces, DTOs, validators, Result pattern |
| Infrastructure | EF Core, Dapper, repositories, email service, OpenAI integration |
| API | Controllers, middleware, request routing, HTTP response mapping |
Additional patterns in use:
- Result pattern — services return
Result<T>instead of throwing exceptions for flow control - Repository + Unit of Work — abstracts data access (both EF Core and Dapper)
- Value objects — domain validation is encapsulated in value object constructors
- Background service — email sending is offloaded to an in-memory queue processed by a hosted service
eShop/
├── eShop.Api.Admin/ # Admin-facing REST API
│ ├── Controllers/
│ ├── Extensions/ # JWT & Swagger configuration
│ ├── Middlewares/ # Global exception handler
│ ├── Responses/ # ApiResponse<T> envelope
│ └── Program.cs
├── eShop.Api.Customer/ # Customer-facing REST API
│ ├── Controllers/
│ ├── Extensions/
│ ├── Middlewares/
│ ├── Responses/
│ └── Program.cs
├── eShop.AppHost/ # .NET Aspire orchestration host
├── eShop.Application/ # Use cases & business logic
│ ├── Constants/
│ ├── Enums/
│ ├── Exceptions/
│ ├── Extensions/
│ ├── Helpers/
│ ├── Interfaces/
│ ├── Requests/
│ ├── Responses/
│ ├── Services/
│ └── Validations/
├── eShop.Domain/ # Core domain
│ ├── Enums/
│ ├── Exceptions/
│ ├── Helpers/
│ ├── Interfaces/
│ ├── Models/
│ ├── Primitives/
│ └── ValueObjects/
├── eShop.Infrastructure/ # Data access & external services
│ ├── BackgroundServices/
│ ├── Configurations/ # EF Core entity configs
│ ├── Context/
│ ├── IoC/
│ ├── Migrations/
│ ├── Repositories/
│ └── Services/
├── eShop.Application.Tests/
├── eShop.Domain.Tests/
└── eShop.Infrastructure.Tests/
| Concern | Technology |
|---|---|
| Framework | .NET 10, ASP.NET Core |
| Orchestration | .NET Aspire 9.5 |
| ORM | Entity Framework Core 9 (SQL Server) |
| Micro-ORM | Dapper 2.1 |
| Auth | JWT Bearer (Microsoft.AspNetCore.Authentication.JwtBearer) |
| Validation | FluentValidation 12 |
| MailKit / MimeKit | |
| AI | OpenAI GPT-4o-mini (product description generation) |
| API Docs | Swashbuckle (Swagger UI) |
| Testing | xUnit |
| Database | SQL Server |
| Entity | Key Properties |
|---|---|
| User | FullName, Username, Email, PasswordHash, SaltKey, Role (Admin/Customer), IsDeleted |
| Product | ProductName, ProductDescription, UnitPrice, UnitQuantity, Image, CategoryId, IsDeleted |
| Category | CategoryName, Image, ParentCategoryId, Children (hierarchical tree) |
| Basket | UserId, BasketItems |
| BasketItem | ProductId, UnitQuantity |
| Order | UserId, TotalAmount, OrderStatus (Pending/Paid/Cancelled), OrderItems |
| OrderItem | ProductId, UnitQuantity, UnitPrice |
| Comment | ProductId, UserId, CommentText, Rating |
ProductName, ProductDescription, UnitPrice, UnitQuantity, Image, CategoryName, Email, Username, FullName, CommentText
Each value object validates its own invariants on construction and throws a DomainValidationException on invalid input.
BaseEntity—Id (Guid)AuditableBaseEntity— extendsBaseEntitywithCreated,CreatedBy,LastModified,LastModifiedBy(auto-populated on save)
Product, Category, and User are never hard-deleted — they carry an IsDeleted flag.
All endpoints return JSON. The response envelope is:
// Success
{ "data": { ... }, "message": "...", "totalCount": 25 }
// Error
{ "message": "Reason for failure" }
// No content (204)
// empty bodytotalCount and message are omitted when null.
Base URL: http://localhost:{port}/api
Authentication: JWT Bearer — include Authorization: Bearer <token> on all protected routes.
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/auth/admin/login |
None | Authenticate and receive a JWT token |
Login request body:
{ "username": "admin", "password": "Admin@123" }| Method | Route | Description |
|---|---|---|
GET |
/product |
List products (pagination + sorting via query params) |
GET |
/product/{id} |
Get product details |
POST |
/product |
Create a new product |
PUT |
/product/{id} |
Update a product |
DELETE |
/product/{id} |
Soft-delete a product |
GET |
/product/{id}/edit |
Get product data for edit form |
GET |
/product/generate |
Generate an AI product description (GPT-4o-mini) |
| Method | Route | Description |
|---|---|---|
GET |
/category |
List categories |
GET |
/category/{id} |
Get category details |
POST |
/category |
Create a category |
PUT |
/category/{id} |
Update a category |
DELETE |
/category/{id} |
Soft-delete a category and its descendants |
GET |
/category/{id}/edit |
Get category data for edit form |
GET |
/category/tree |
Get full hierarchical category tree |
| Method | Route | Description |
|---|---|---|
GET |
/dashboard/orders-today |
Total orders placed today |
GET |
/dashboard/revenue-today |
Total revenue for today |
GET |
/dashboard/total-customers |
Total registered customers |
Base URL: http://localhost:{port}/api
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/auth/login |
None | Login and receive a JWT token |
POST |
/auth/register |
None | Register a new customer account |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/product |
None | Browse products with filters and pagination |
GET |
/product/{id} |
None | Get product details (includes user-specific data if authenticated) |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/category/category-tree-for-menu |
None | Get category tree for navigation menu |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/basket |
Customer | Get current user's basket |
POST |
/basket/merge |
Customer | Add or update items in the basket |
DELETE |
/basket/items |
Customer | Clear all items from the basket |
DELETE |
/basket/items/{productId} |
Customer | Remove a specific item from the basket |
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/order |
Customer | Place an order (sends confirmation email) |
GET |
/order |
Customer | Get order history for the current user |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/comment |
None | Get comments for a product |
POST |
/comment |
Customer | Post a comment with a rating on a product |
JWT Bearer tokens are used across both APIs.
- Tokens are generated on login and must be sent in the
Authorization: Bearer <token>header - Role-based authorization:
Adminrole for the Admin API,Customerrole for protected Customer endpoints - Token lifetime validation is enabled; issuer/audience validation is disabled (configurable)
Password requirements: minimum 4 characters, at least one uppercase letter, one lowercase letter, one digit, and one special character.
Password hashing: PBKDF2-SHA256 with 100,000 iterations and a 16-byte random salt per user.
FluentValidation runs automatically on all requests. Invalid input returns 400 Bad Request:
{ "message": "Validation error description" }Unhandled exceptions are caught by the global exception handler middleware:
| Exception | HTTP Status | Message |
|---|---|---|
DomainValidationException |
400 | Domain rule violation message |
ExternalDependencyException |
503 | "Service temporarily unavailable." |
| Any other | 500 | "An unexpected error occurred." |
- .NET 10 SDK
- SQL Server (local or remote)
- (Optional) .NET Aspire workload for orchestrated startup
Both API projects require an appsettings.json (or user secrets / environment variables) with the following keys:
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=eShopDb;Trusted_Connection=True;"
},
"Jwt": {
"SecretKey": "your-secret-key-at-least-32-characters"
},
"SeedAdmin": {
"Password": "Admin@123"
},
"OpenAI": {
"ApiKey": "your-openai-api-key"
},
"EmailSettings": {
"Host": "smtp.example.com",
"Port": 587,
"Username": "[email protected]",
"Password": "your-email-password"
}
}Option 1 — With .NET Aspire (recommended):
dotnet run --project eShop.AppHostThis starts both APIs and the Aspire dashboard in one command.
Option 2 — Run APIs individually:
# Apply migrations (run once)
dotnet ef database update --project eShop.Infrastructure --startup-project eShop.Api.Admin
# Admin API
dotnet run --project eShop.Api.Admin
# Customer API
dotnet run --project eShop.Api.CustomerSwagger UI is available at /swagger in the Development environment.
A default admin user is seeded on first startup using the SeedAdmin:Password configuration value.
dotnet testTest projects:
| Project | Covers |
|---|---|
eShop.Domain.Tests |
Domain entities and value objects |
eShop.Application.Tests |
Application service logic |
eShop.Infrastructure.Tests |
Infrastructure integration tests |
- Hierarchical categories — categories support a parent/child tree structure with cascading soft-deletes
- AI product descriptions — admins can generate product descriptions via GPT-4o-mini
- Shopping basket — supports guest-to-authenticated basket merging
- Order placement — places an order, reduces basket, and sends an HTML confirmation email asynchronously
- Email queue — emails are queued in-memory and dispatched by a background hosted service, keeping order placement fast
- Dual data access — EF Core for writes and complex queries, Dapper for lightweight read queries
- Auditing — all auditable entities automatically track creation and modification timestamps and user
- Clean separation of concerns — Admin and Customer APIs share the same Application and Infrastructure layers but have completely separate controllers, DTOs, and service interfaces