A production-ready, enterprise-grade REST API built with ASP.NET Core (.NET 10) following Clean Architecture, SOLID principles, CQRS pattern, and Domain-Driven Design (DDD).
- Overview
- Architecture Patterns
- Key Features
- Technology Stack
- Project Structure
- Data Provider Selection
- Enterprise Features
- Getting Started
- API Documentation
- CQRS Implementation
- SOLID Principles
- Contributing
- License
This project serves as a comprehensive template for building enterprise-grade APIs with modern .NET development practices. It demonstrates how to build a secure, maintainable, and highly scalable REST API using industry-standard patterns and principles
- Clean Architecture - Separation of concerns with clear boundaries
- CQRS (Command Query Responsibility Segregation) - Separate read and write operations
- MediatR - Mediator pattern for decoupled request/response handling
- SOLID Principles - Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion
- Domain-Driven Design - Rich domain models with domain events
- Repository Pattern - Data access abstraction
- Unit of Work Pattern - Transaction management
- Result Pattern - Standardized response handling
- CQRS with MediatR - Commands and Queries separation
- FluentValidation - Automatic request validation pipeline
- Pipeline Behaviors - Logging, Performance monitoring, Validation
- Domain Events - Event-driven architecture support
- JWT Authentication - Secure token-based authentication
- Role-Based Authorization - Fine-grained access control
- Global Exception Handling - Centralized error management
- Request Logging Middleware - Comprehensive request/response logging
- Swagger/OpenAPI - Interactive API documentation
- Entity Framework Core - Code-first database approach
- Multi-Provider Data Access - Choose between Entity Framework, Dapper, or ADO.NET via config
- Health Checks - Database and API health monitoring at
/health - Rate Limiting - Fixed window, sliding window, and token bucket policies
- API Versioning - Query string and header-based version control
- OpenTelemetry - Distributed tracing and metrics instrumentation
- Output Caching - Response caching with tag-based invalidation
- Refresh Tokens - Secure token rotation for long-lived sessions
- Pagination - Built-in
pageNumberandpageSizequery parameters
- ASP.NET Core Web API (.NET 10)
- MediatR (12.4.1) - CQRS implementation
- FluentValidation (11.11.0) - Request validation
- Entity Framework Core (10.0.5) - Full ORM with change tracking & migrations
- Dapper (2.1.72) - Lightweight micro-ORM with raw SQL
- ADO.NET (Microsoft.Data.SqlClient) - Pure data access, no ORM
- SQL Server
- JWT Bearer Authentication + Refresh Tokens
- BCrypt.Net - Secure password hashing
- Mapster (7.4.0) - Object mapping
- Swagger/OpenAPI - API documentation
- OpenTelemetry (1.15.0) - Distributed tracing & metrics
- Asp.Versioning (8.1.1) - API version management
- AspNetCore.HealthChecks.SqlServer (9.0.0) - Database health monitoring
Responsibilities:
- HTTP request/response handling
- Controllers (thin, delegating to MediatR)
- Middleware (Exception handling, Request logging)
- Dependency injection configuration
- Authentication & Authorization setup
- Swagger configuration
Key Files:
Controllers/- API endpoints using MediatRMiddleware/- Custom middleware componentsProgram.cs- Application startup and DI configuration
Responsibilities:
- CQRS Commands and Queries
- Command/Query Handlers
- Validators (FluentValidation)
- Pipeline Behaviors
- Application interfaces
- DTOs and Response models
- Business logic orchestration
Structure:
Application/
βββ Common/
β βββ Behaviours/ # MediatR pipeline behaviors
β βββ Exceptions/ # Custom exceptions
β βββ Interfaces/ # Application contracts
β βββ Models/ # Result pattern, common models
βββ Features/
β βββ Authentication/
β β βββ Commands/ # Register, Login commands
β β βββ EventHandlers/ # Domain event handlers
β βββ Tasks/
β βββ Commands/ # Create, Update, Delete commands
β βββ Queries/ # GetAll, GetById queries
β βββ EventHandlers/ # Task-related event handlers
βββ DependencyInjection.cs # Application services registration
Responsibilities:
- Core business entities
- Domain events
- Business rules and invariants
- Value objects
- Domain interfaces
Structure:
Domain/
βββ Common/
β βββ BaseEntity.cs # Base entity with domain events
β βββ BaseAuditableEntity.cs # Auditable entity base
β βββ IDomainEvent.cs # Domain event interface
βββ Entities/
β βββ AppUser.cs
β βββ TaskItem.cs
βββ Events/
βββ UserRegisteredEvent.cs
βββ TaskCreatedEvent.cs
Responsibilities:
- Database context implementation
- Repository implementations (EF Core, Dapper, ADO.NET)
- Unit of Work implementation
- Data provider selection via configuration
- Data persistence
Structure:
Infrastructure/
βββ Data/
β βββ AppDbContext.cs # EF Core DbContext with domain events
β βββ SqlConnectionFactory.cs # IDbConnection factory (Dapper & ADO.NET)
βββ Persistence/
β βββ UnitOfWork.cs # EF Core transaction management
β βββ DapperUnitOfWork.cs # Dapper/ADO transaction management
βββ Repositories/
β βββ EntityFramework/
β β βββ EfTaskRepository.cs # EF Core implementation
β β βββ EfUserRepository.cs
β βββ Dapper/
β β βββ DapperTaskRepository.cs # Dapper implementation
β β βββ DapperUserRepository.cs
β βββ Ado/
β βββ AdoTaskRepository.cs # Pure ADO.NET implementation
β βββ AdoUserRepository.cs
βββ Migrations/
βββ DependencyInjection.cs # Provider selection & DI registration
API Layer β Application Layer β Domain Layer
Infrastructure Layer β Application Layer + Domain Layer
Key Principle: Dependencies point inward. The Domain layer has no dependencies. The Application layer depends only on Domain. Infrastructure implements Application interfaces.
This project supports three data access strategies. You choose which one to use by setting a single value in appsettings.json β no code changes required.
Edit DotnetEnterpriseApi.Api/appsettings.json:
{
"DataProvider": "EntityFramework"
}| Value | Description | Best For |
|---|---|---|
EntityFramework |
EF Core with DbContext, change tracking, LINQ, migrations | Rapid development, complex queries, automatic schema management |
Dapper |
Micro-ORM with raw SQL and automatic object mapping | High-performance reads, fine-tuned SQL control |
Ado |
Pure ADO.NET with SqlCommand/SqlDataReader, no ORM | Maximum control, zero abstraction overhead |
appsettings.json ["DataProvider"]
β
βΌ
DependencyInjection.cs (Infrastructure)
β
βββ "EntityFramework"
β βββ AppDbContext (EF Core)
β βββ UnitOfWork (EF transactions)
β βββ EfTaskRepository
β βββ EfUserRepository
β
βββ "Dapper"
β βββ SqlConnectionFactory (IDbConnection)
β βββ DapperUnitOfWork
β βββ DapperTaskRepository
β βββ DapperUserRepository
β
βββ "Ado"
βββ SqlConnectionFactory (IDbConnection)
βββ DapperUnitOfWork
βββ AdoTaskRepository
βββ AdoUserRepository
All three providers implement the same interfaces (ITaskRepository, IUserRepository, IUnitOfWork), so the Application and API layers remain unchanged regardless of which provider is active.
HTTP Request
β
βΌ
Controller (TasksController / AuthController)
β
βΌ
MediatR.Send(Command or Query)
β
βΌ
Handler (uses ITaskRepository / IUserRepository)
β
βΌ
Repository Implementation (EF / Dapper / ADO β selected at startup)
β
βΌ
SQL Server Database
β
βΌ
Result<T> flows back up through the same chain
β
βΌ
HTTP Response (JSON)
| Feature | EntityFramework | Dapper | ADO.NET |
|---|---|---|---|
| Change Tracking | Yes | No | No |
| LINQ Support | Yes | No | No |
| Raw SQL | Optional | Yes | Yes |
| Object Mapping | Automatic | Automatic | Manual |
| Migrations | Yes | No | No |
| Performance | Good | Excellent | Excellent |
| Code Verbosity | Low | Medium | High |
| Learning Curve | Medium | Low | Low |
Get Task by ID
Entity Framework:
return await _context.Tasks.FindAsync(id);Dapper:
const string sql = "SELECT Id, Title, Description, IsCompleted, CreatedDate FROM Tasks WHERE Id = @Id";
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryFirstOrDefaultAsync<TaskItem>(sql, new { Id = id });ADO.NET:
const string sql = "SELECT Id, Title, Description, IsCompleted, CreatedDate FROM Tasks WHERE Id = @Id";
using var connection = (SqlConnection)_connectionFactory.CreateConnection();
await connection.OpenAsync();
using var command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@Id", id);
using var reader = await command.ExecuteReaderAsync();
return await reader.ReadAsync() ? MapTaskItem(reader) : null;All features below are built-in and ready to use out of the box. No extra setup required.
Endpoint: GET /health
Returns JSON with API and database health status:
{
"status": "Healthy",
"checks": [
{
"name": "sqlserver",
"status": "Healthy",
"duration": "184ms"
}
],
"totalDuration": "188ms"
}Protects your API from abuse. Three built-in policies:
| Policy | Type | Limit | Window |
|---|---|---|---|
fixed |
Fixed Window | 100 requests | 1 minute |
sliding |
Sliding Window | 50 requests | 1 minute |
token |
Token Bucket | 100 tokens, 10/10s refill | Continuous |
A global limiter (200 req/min per user/IP) is applied to all endpoints automatically. When the limit is exceeded, the API returns 429 Too Many Requests.
Supports two methods simultaneously:
# Query string
GET /api/tasks?api-version=1.0
# Header
GET /api/tasks
X-Api-Version: 1.0
Response headers include api-supported-versions to tell clients which versions are available.
Built-in distributed tracing and metrics:
- Traces - Every HTTP request is traced with span context
- Metrics - ASP.NET Core and HTTP client metrics collected automatically
- Console Exporter - Outputs to console by default (replace with Jaeger, Zipkin, OTLP, or Application Insights for production)
To switch to a production exporter, replace AddConsoleExporter() in Program.cs with your preferred exporter package.
Response caching reduces database load:
| Policy | TTL | Description |
|---|---|---|
| Default | 30 seconds | Applied to all endpoints |
tasks |
60 seconds | Tag-based, applied to task listing |
Cache is automatically invalidated when data changes.
Login now returns both a JWT access token and a refresh token:
{
"token": "eyJhbG...",
"refreshToken": "FyuFem...",
"userName": "john",
"email": "john@example.com",
"role": "User"
}Use POST /api/auth/refresh-token with the refresh token to get new tokens without re-entering credentials. Refresh tokens expire in 7 days and are single-use (revoked after each rotation).
All list endpoints support pagination via query parameters:
GET /api/tasks?pageNumber=1&pageSize=10
| Parameter | Default | Description |
|---|---|---|
pageNumber |
1 | Page number (1-based) |
pageSize |
10 | Items per page |
- Each handler handles one command/query
- Separate validators for each request
- Dedicated event handlers for domain events
- Pipeline behaviors extend functionality without modifying existing code
- Base classes allow extension through inheritance
- Interface-based design allows substitution
- Repository pattern enables different implementations
- Small, focused interfaces (IApplicationDbContext, IUnitOfWork)
- Clients depend only on interfaces they use
- High-level modules depend on abstractions
- Dependency injection throughout
- Infrastructure implements Application interfaces
Commands modify state and return a Result object:
Example: Register User Command
// Command
public class RegisterCommand : IRequest<Result<RegisterResponse>>
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
// Handler
public class RegisterCommandHandler : IRequestHandler<RegisterCommand, Result<RegisterResponse>>
{
// Implementation with validation, business logic, and persistence
}
// Validator
public class RegisterCommandValidator : AbstractValidator<RegisterCommand>
{
// FluentValidation rules
}Queries retrieve data without modifying state:
Example: Get All Tasks Query
// Query
public class GetAllTasksQuery : IRequest<Result<List<TaskResponse>>>
{
}
// Handler
public class GetAllTasksQueryHandler : IRequestHandler<GetAllTasksQuery, Result<List<TaskResponse>>>
{
// Implementation to retrieve and return data
}Automatic cross-cutting concerns applied to all requests:
- ValidationBehaviour - Validates requests using FluentValidation
- LoggingBehaviour - Logs request execution
- PerformanceBehaviour - Monitors long-running requests (>500ms)
- User registers with email and password
- Password is securely hashed using BCrypt
- User logs in using credentials
- API generates a JWT token with claims
- Client sends JWT token in Authorization header
- Protected endpoints validate the token
- Role-based authorization controls access
Example Authorization Header:
Authorization: Bearer {your_jwt_token}
POST /api/auth/register
- Register a new user
- Request body:
{ "userName": "string", "email": "string", "password": "string" } - Response:
Result<RegisterResponse>
POST /api/auth/login
- Login and receive JWT token
- Request body:
{ "email": "string", "password": "string" } - Response:
Result<LoginResponse>with JWT + Refresh Token
POST /api/auth/refresh-token
- Exchange a refresh token for new JWT + refresh token pair
- Request body:
{ "token": "string" } - Response:
Result<LoginResponse>with new tokens
GET /health
- Returns API and database health status (no auth required)
GET /api/tasks?pageNumber=1&pageSize=10
- Get all tasks with pagination
- Response:
Result<List<TaskResponse>>
GET /api/tasks/{id}
- Get task by ID
- Response:
Result<TaskResponse>
POST /api/tasks
- Create new task
- Request body:
{ "title": "string", "description": "string" } - Response:
Result<TaskResponse>
PUT /api/tasks/{id}
- Update task
- Request body:
{ "title": "string", "description": "string", "isCompleted": boolean } - Response:
Result<TaskResponse>
DELETE /api/tasks/{id} (Admin only)
- Delete task
- Response:
Result
- .NET 10 SDK
- SQL Server or SQL Server LocalDB
- Visual Studio 2022 or VS Code
-
Clone the repository
git clone https://github.com/aadityamp01/dotnet-enterprise-api.git cd dotnet-enterprise-api -
Update database connection string
Edit
DotnetEnterpriseApi.Api/appsettings.json:"ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=DotNetEnterpriseApiDb;Trusted_Connection=True;TrustServerCertificate=True;" }
-
Update JWT settings
Edit
DotnetEnterpriseApi.Api/appsettings.json:"Jwt": { "Key": "YourSuperSecretKeyHere_MinimumLength32Characters!", "Issuer": "DotNetEnterpriseAPI", "Audience": "DotNetEnterpriseAPIUsers" }
-
Apply database migrations
cd DotnetEnterpriseApi.Api dotnet ef database update -
Run the application
dotnet run
-
Access Swagger UI
Navigate to:
https://localhost:5001/swagger
POST /api/auth/register
Content-Type: application/json
{
"userName": "john_doe",
"email": "john@example.com",
"password": "SecurePass123"
}Response:
{
"isSuccess": true,
"message": "User registered successfully",
"data": {
"id": 1,
"userName": "john_doe",
"email": "john@example.com"
},
"errors": []
}POST /api/auth/login
Content-Type: application/json
{
"email": "john@example.com",
"password": "SecurePass123"
}Response:
{
"isSuccess": true,
"message": "Login successful",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userName": "john_doe",
"email": "john@example.com",
"role": "User"
},
"errors": []
}All task endpoints require authentication. Include the JWT token in the Authorization header:
Authorization: Bearer {your_jwt_token}
GET /api/tasks
Authorization: Bearer {token}GET /api/tasks/{id}
Authorization: Bearer {token}POST /api/tasks
Authorization: Bearer {token}
Content-Type: application/json
{
"title": "Complete project documentation",
"description": "Write comprehensive README and API docs"
}PUT /api/tasks/{id}
Authorization: Bearer {token}
Content-Type: application/json
{
"title": "Updated title",
"description": "Updated description",
"isCompleted": true
}DELETE /api/tasks/{id}
Authorization: Bearer {token}- JWT Authentication - Secure token-based authentication
- Password Hashing - BCrypt with salt for password security
- Role-Based Authorization - Fine-grained access control
- Input Validation - FluentValidation prevents injection attacks
- HTTPS - Enforced encrypted communication
# Run unit tests
dotnet test
# Run with coverage
dotnet test /p:CollectCoverage=true- Pipeline Behaviors - Automatic performance monitoring
- Async/Await - Non-blocking I/O operations
- CQRS - Optimized read and write operations
- Logging - Performance tracking for requests >500ms
- Clean Architecture by Robert C. Martin
- CQRS Pattern
- Domain-Driven Design by Eric Evans
- ASP.NET Core Team
Built with β€οΈ using Clean Architecture principles