Proof of Concept for a RESTful API built with .NET 10 (LTS) and ASP.NET Core. Manage football player data with SQLite, Entity Framework Core, Swagger documentation, and in-memory caching.
- Features
- Tech Stack
- Project Structure
- Architecture
- Architecture Decisions
- API Reference
- Prerequisites
- Quick Start
- Testing
- Containers
- Releases
- Environment Variables
- Command Summary
- Contributing
- Legal
- ποΈ Clean layered architecture - Repository pattern, dependency injection, and async operations throughout
- π Interactive API exploration - Swagger UI documentation with health monitoring endpoints
- β‘ Performance optimizations - In-memory caching, rate limiting, and efficient database queries
- π§ͺ High test coverage - xUnit tests with automated reporting to Codecov and SonarCloud
- π Token-efficient documentation - Custom instructions with coding guidelines, architecture rules, and agent workflows for AI-assisted development
- π³ Full containerization - Multi-stage Docker builds with Docker Compose orchestration
- π Complete CI/CD pipeline - Automated testing, code quality checks, Docker publishing, and GitHub releases
- ποΈ Stadium-themed semantic versioning - Memorable, alphabetical release names from World Cup venues
| Category | Technology |
|---|---|
| Framework | .NET 10 (LTS) |
| Web Framework | ASP.NET Core 10.0 |
| API Documentation | Swashbuckle (OpenAPI 3.0) |
| Validation | FluentValidation 12 |
| Mapping | AutoMapper 14 |
| Database | SQLite 3 |
| ORM | Entity Framework Core 10.0 |
| Logging | Serilog 9 |
| Testing | xUnit, Moq, FluentAssertions |
| Containerization | Docker & Docker Compose |
src/Dotnet.Samples.AspNetCore.WebApi/
βββ Program.cs # Entry point: DI setup, middleware pipeline
βββ Controllers/ # HTTP handlers (request/response logic)
β βββ PlayerController.cs
βββ Services/ # Business logic + caching layer
β βββ IPlayerService.cs
β βββ PlayerService.cs
βββ Repositories/ # Data access abstraction
β βββ IPlayerRepository.cs
β βββ IRepository.cs
β βββ PlayerRepository.cs
β βββ Repository.cs
βββ Models/ # Domain entities and DTOs
β βββ Player.cs
β βββ PlayerRequestModel.cs
β βββ PlayerResponseModel.cs
βββ Data/ # EF Core DbContext and migrations
β βββ PlayerDbContext.cs
βββ Mappings/ # AutoMapper profiles
β βββ PlayerMappingProfile.cs
βββ Validators/ # FluentValidation rules
β βββ PlayerRequestModelValidator.cs
βββ Configurations/ # Swagger, rate limiting config
βββ Enums/ # Domain enumerations (e.g. Position)
βββ Extensions/ # Service registration extensions
βββ Middlewares/ # Custom ASP.NET Core middleware
βββ Utilities/ # Helper classes
βββ Migrations/ # EF Core migrations
βββ storage/ # Pre-seeded SQLite database
test/Dotnet.Samples.AspNetCore.WebApi.Tests/
βββ Unit/ # Unit tests with xUnit
β βββ PlayerControllerTests.cs
β βββ PlayerServiceTests.cs
β βββ PlayerValidatorTests.cs
βββ Utilities/ # Shared test helpers
βββ DatabaseFakes.cs
βββ PlayerFakes.cs
βββ PlayerMocks.cs
βββ PlayerStubs.cs
Layered architecture with dependency injection via constructors and interface-based contracts.
%%{init: {
"theme": "default",
"themeVariables": {
"fontFamily": "Fira Code, Consolas, monospace",
"textColor": "#555",
"lineColor": "#555",
"clusterBkg": "#f5f5f5",
"clusterBorder": "#ddd"
}
}}%%
graph RL
Tests[Tests]
subgraph Layer1[" "]
Program[Program]
Serilog[Serilog]
Swashbuckle[Swashbuckle]
end
subgraph Layer2[" "]
Controllers[Controllers]
Validators[Validators]
FluentValidation[FluentValidation]
AspNetCore[ASP.NET Core]
end
subgraph Layer3[" "]
Services[Services]
Mappings[Mappings]
AutoMapper[AutoMapper]
MemoryCache[MemoryCache]
end
subgraph Layer4[" "]
Repositories[Repositories]
Data[Data]
EFCore[EF Core]
end
Models[Models]
%% Strong dependencies
%% Layer 1
Controllers --> Program
Serilog --> Program
Swashbuckle --> Program
%% Layer 2
Services --> Controllers
Validators --> Controllers
FluentValidation --> Validators
AspNetCore --> Controllers
%% Layer 3
Repositories --> Services
MemoryCache --> Services
Mappings --> Services
AutoMapper --> Mappings
Models --> Mappings
%% Layer 4
Models --> Repositories
Models --> Data
Data --> Repositories
EFCore --> Data
EFCore -.-> Repositories
%% Soft dependencies
Services -.-> Tests
Controllers -.-> Tests
%% Node styling with stroke-width
classDef core fill:#b3d9ff,stroke:#6db1ff,stroke-width:2px,color:#555,font-family:monospace;
classDef deps fill:#ffcccc,stroke:#ff8f8f,stroke-width:2px,color:#555,font-family:monospace;
classDef test fill:#ccffcc,stroke:#53c45e,stroke-width:2px,color:#555,font-family:monospace;
classDef feat fill:#ffffcc,stroke:#fdce15,stroke-width:2px,color:#555,font-family:monospace;
class Data,Models,Repositories,Services,Controllers,Program,Validators,Mappings core;
class AutoMapper,FluentValidation,Serilog,Swashbuckle deps;
class Tests test;
class AspNetCore,EFCore,MemoryCache feat;
Arrows follow the injection direction: A --> B means A is injected into B. Solid arrows (-->) represent active ASP.NET Core dependencies β services registered with the IoC container and invoked at runtime. Dotted arrows (-.->) represent test dependencies β test classes reference the types they exercise but are not injected into them.
Program is the composition root: it registers all services, repositories, DbContext, mappers, validators, and middleware with the ASP.NET Core IoC container, which resolves them at runtime via constructor injection.
Four layers: Initialization (Program), HTTP (Controllers, Validators), Business (Services, Mappings), and Data (Repositories, Data).
Framework and third-party packages are placed inside the subgraph of the layer that uses them β Serilog and Swashbuckle in Initialization, ASP.NET Core and FluentValidation in HTTP, AutoMapper in Business, EF Core in Data.
Models is a cross-cutting type concern β shared entities and DTOs consumed across all layers, with no logic of its own.
Blue = core application packages, yellow = Microsoft platform packages, red = third-party libraries, green = tests.
Simplified, conceptual view β not all components or dependencies are shown.
See Architecture Decision Records (ADRs) for documented decisions about this project's architecture, technology choices, and development practices.
Interactive API documentation is available via Swagger UI at https://localhost:9000/swagger/index.html when the server is running.
π‘ Swagger documentation is only available in development mode for security reasons.
Quick Reference:
GET /players- List all playersGET /players/{id:Guid}- Get player by ID (requires authentication)GET /players/squadNumber/{squadNumber:int}- Get player by squad numberPOST /players- Create new playerPUT /players/squadNumber/{squadNumber:int}- Update playerDELETE /players/squadNumber/{squadNumber:int}- Remove playerGET /health- Health check
For complete endpoint documentation with request/response schemas, explore the interactive Swagger UI.
Before you begin, ensure you have the following installed:
-
.NET 10 SDK (LTS) or higher
-
Docker Desktop (optional, for containerized deployment)
-
dotnet-ef CLI tool (for database migrations)
dotnet tool install --global dotnet-ef
git clone https://github.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi.git
cd Dotnet.Samples.AspNetCore.WebApidotnet watch run --project src/Dotnet.Samples.AspNetCore.WebApi/Dotnet.Samples.AspNetCore.WebApi.csprojThe server will start on https://localhost:9000.
- API:
https://localhost:9000 - Swagger Documentation:
https://localhost:9000/swagger/index.html - Health Check:
https://localhost:9000/health
Run the test suite with xUnit:
# Run all tests
dotnet test
# Run tests with coverage report
dotnet test --results-directory "coverage" --collect:"XPlat Code Coverage" --settings .runsettings
# View coverage report
dotnet tool install --global dotnet-reportgenerator-globaltool
reportgenerator -reports:coverage/**/coverage.cobertura.xml -targetdir:coverage -reporttypes:HtmlTests are located in the test/ directory and use xUnit for unit testing. Coverage reports are generated for controllers and services only.
This project includes full Docker support with multi-stage builds and Docker Compose for easy deployment.
docker compose builddocker compose upπ‘ On first run, the container copies a pre-seeded SQLite database into a persistent volume. On subsequent runs, that volume is reused and the data is preserved.
docker compose downTo remove the volume and reinitialize the database from the built-in seed file:
docker compose down -vThe containerized application runs on port 9000 and includes health checks that monitor the /health endpoint.
This project uses stadium-themed release names inspired by famous football stadiums that hosted FIFA World Cup matches. Each release is named after a stadium (A-Z alphabetically), making versions memorable and fun.
Releases follow the pattern: v{SEMVER}-{STADIUM} (e.g., v1.0.0-azteca)
- Semantic Version: Standard versioning (MAJOR.MINOR.PATCH)
- Stadium Name: Alphabetically ordered codename from the stadium list
To create a new release, follow this workflow:
First, create a release/ branch and document your changes in CHANGELOG.md:
git checkout -b release/1.0.0-azteca
# Move items from [Unreleased] to new release section
# Example: [1.0.0 - azteca] - 2026-01-22
git add CHANGELOG.md
git commit -m "docs: prepare changelog for v1.0.0-azteca release"
git push origin release/1.0.0-azteca
# Open a PR, get it reviewed, and merge into masterThen create and push the version tag:
git tag -a v1.0.0-azteca -m "Release 1.0.0 - Azteca"
git push origin v1.0.0-aztecaThis triggers the CD workflow which automatically:
- Validates the stadium name
- Builds and tests the project in Release configuration
- Publishes Docker images to GitHub Container Registry with three tags
- Creates a GitHub Release with auto-generated changelog from commits
π‘ Always update CHANGELOG.md before creating the tag. See CHANGELOG.md for detailed release instructions.
Each release publishes multiple tags for flexibility:
# By semantic version (recommended for production)
docker pull ghcr.io/nanotaboada/dotnet-samples-aspnetcore-webapi:1.0.0
# By stadium name (memorable alternative)
docker pull ghcr.io/nanotaboada/dotnet-samples-aspnetcore-webapi:azteca
# Latest release
docker pull ghcr.io/nanotaboada/dotnet-samples-aspnetcore-webapi:latestπ‘ See CHANGELOG.md for the complete stadium list (A-Z) and release history.
The application can be configured using environment variables for different scenarios:
For local development and debugging:
# ASP.NET Core environment mode
ASPNETCORE_ENVIRONMENT=Development
# Server URLs
ASPNETCORE_URLS=https://localhost:9000
# Show detailed error messages
ASPNETCORE_DETAILEDERRORS=1
# Graceful shutdown timeout
ASPNETCORE_SHUTDOWNTIMEOUTSECONDS=3For production deployment:
# Database storage path
# Points to the persistent Docker volume
STORAGE_PATH=/storage/players-sqlite3.dbπ‘ Additional environment variables (
ASPNETCORE_ENVIRONMENT=ProductionandASPNETCORE_URLS=http://+:9000) are set in theDockerfile.
| Command | Description |
|---|---|
dotnet watch run --project src/... |
Start development server with hot reload |
dotnet build |
Build the solution |
dotnet test |
Run all tests |
dotnet test --collect:"XPlat Code Coverage" |
Run tests with coverage report |
dotnet csharpier . |
Format source code |
dotnet ef migrations add <Name> |
Create a new migration |
dotnet ef database update |
Apply migrations |
./scripts/run-migrations-and-copy-database.sh |
Regenerate database with seed data |
docker compose build |
Build Docker image |
docker compose up |
Start Docker container |
docker compose down |
Stop Docker container |
docker compose down -v |
Stop and remove Docker volume |
Contributions are welcome! Please read CONTRIBUTING.md for details on the code of conduct and the process for submitting pull requests.
Key guidelines:
- Follow Conventional Commits for commit messages
- Ensure all tests pass (
dotnet test) - Keep changes small and focused
- Review .github/copilot-instructions.md for architectural patterns
This project is provided for educational and demonstration purposes and may be used in production at your own discretion. All trademarks, service marks, product names, company names, and logos referenced herein are the property of their respective owners and are used solely for identification or illustrative purposes.