DEV Community

Cover image for Clean Architecture for .NET API + Blazor Server: A Practical, Testable Template
raj preetam singh
raj preetam singh

Posted on

Clean Architecture for .NET API + Blazor Server: A Practical, Testable Template

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) |
+-------------------+
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)