Clean Architecture helps keep large applications maintainable by enforcing clear boundaries between UI, business rules, and infrastructure. This post walks through a pragmatic setup for a .NET API paired with Blazor Server using a classic three-layer dependency flow, a scalable folder structure, and a concrete use case wired end-to-end. It’s designed so features are testable, infrastructure is replaceable, and the UI stays clean.
Why this matters
Maintainability: Boundaries prevent “logic leakage” from UI to data access.
Testability: Use cases are pure and easy to unit test.
Flexibility: Infrastructure (DBs, HTTP clients) can be swapped without touching business logic.
Velocity: Teams can work in parallel on Presentation, Application, and Infrastructure without stepping on each other.
Clean Architecture dependency flow
+-------------------+
| Presentation |
| (API / Blazor) |
+-------------------+
|
calls UseCases
|
+-------------------+
| Application |
| (UseCases & Ports)|
+-------------------+
|
uses Interfaces
|
+-------------------+
| Infrastructure |
| (Services/DB/API) |
+-------------------+
Allowed references
API depends on Application
Application depends on Interfaces (ports)
Infrastructure implements Interfaces (adapters) and is injected at runtime
This enforces the Dependency Inversion Principle: inner layers don’t know about outer layers, and the Application layer orchestrates policy, not plumbing.
Recommended folder structure
/Portal
├── /API
│ ├── Controllers
│ └── Program.cs
├── /Application
│ ├── DTOs
│ ├── Interfaces
│ │ ├── Services
│ │ └── UseCases
│ ├── UseCases
│ │ └── NameSpace
│ └── Wrappers
├── /Domain (optional)
│ └── Entities
├── /Infrastructure
│ ├── Services
│ │ └── MyService.cs
│ └── HttpClients / DbContexts
└── /Tests
├── UnitTests
└── IntegrationTests
Notes
Presentation holds only UI and minimal endpoint wiring.
Application holds business rules, use cases, and abstract interfaces (ports).
Infrastructure fulfills those interfaces (adapters): HTTP clients, DB repositories, external APIs.
Domain is optional if you keep entities and domain logic separate.
Sample use case: Query Strike Participation by date
Application layer (the use case)
Define the port (use case contract) and implement it with orchestration logic only—no HTTP/DB details.
The use case depends only on an interface. This makes it unit-test friendly.
Infrastructure layer (service implementation)
Implement the interface using HttpClient/DB; map raw errors into a Result wrapper to keep the application pure.
API layer (minimal API mapping)
The API simply wires HTTP to the use case and returns the result—no business logic.
Benefits
Test the use case in isolation with a fake IStrikeParticipationService.
Swap HTTP implementation with a database or a mock adapter without touching the Application layer.
Keep Program.cs focused on composition and DI.
How Blazor Server fits in
Blazor Server lives in Presentation, side‑by‑side with or behind the API.
Components call the API (or directly call use cases if hosted in the same process and you want zero-HTTP boundaries).
Keep Blazor pages/components dumb: render state, dispatch intents, and let use cases do the orchestration.
Example Blazor component snippet (calling the API)
Tip: If Blazor and API are in the same host, inject IGetStrikeParticipationByDateUseCase directly into the component for zero‑overhead calls; if you need separation (scalability, security, versioning), go via the API.
Testing strategy
Unit Tests (Application): Mock dependencies (e.g., IDataService) to test use case logic independently and deterministically.
Integration Tests (API): Use WebApplicationFactory to spin up real API endpoints; replace implementations with fakes for control.
Contract Tests (Infrastructure): Validate requests/responses against external services with sandbox environments or mocks.
These tests are fast, deterministic, and cover core business flow.
Integration tests (API)
Use WebApplicationFactory to spin up the API.
Replace StrikeParticipationService with a fake via DI for deterministic responses.
Verify endpoint contracts (status codes, shapes, pagination, errors).
Contract tests (Infrastructure)
If calling real external APIs, add a suite that runs against a sandbox/stub to validate request/response compatibility.
Scaling and evolving the template
Add more use cases without touching controllers.
Swap infrastructure adapters (HTTP → DB) without touching use cases.
Add caching at the Infrastructure layer (e.g., Redis) behind the same interface.
Introduce CQRS if needed: query use cases for reads and command use cases for writes.
Add validation at Application boundaries using a validator (FluentValidation) before invoking services.
Guardrails and best practices
Keep Presentation free of business logic; it orchestrates input/output and delegates to use cases.
Keep Application free of infrastructure knowledge; it depends on interfaces (ports) only.
Keep Infrastructure ignorant of UI; it implements ports and handles external concerns.
Favor constructor injection and small, composable services.
Centralize error/result handling with a Result wrapper for consistent behavior across layers.
Closing
This Clean Architecture setup gives a clear, testable skeleton for teams shipping .NET + Blazor Server apps. The use case pattern keeps business logic cohesive and portable; the interface‑driven infrastructure keeps dependencies honest and swappable; the API stays thin and reliable.
Want a starter repo or diagram export? Drop a comment, and a downloadable template and dependency diagram can be shared.
Top comments (0)