A layered .NET platform for workforce management: a REST API, a client application, and background workers over a relational data layer.
About this document. The implementation is private client work under NDA. This case study describes the architecture and the reasoning behind it. It contains no source code, client identifiers, schema, or data. Written by the engineer who built it.
flowchart TD
Client[Client Application] -->|HTTPS| API[REST API<br/>controllers · middleware · attributes]
Workers[Background Workers<br/>hosted services] --> Services
API --> Services[Application Services<br/>operations · domain validators]
Services --> Repos[Repositories<br/>repository pattern]
Repos --> DB[(Relational store<br/>EF Core · migrations)]
Shared[Shared types] -.referenced by.- Services
Infra[Infrastructure<br/>cross-cutting concerns] -.referenced by.- Services
style API fill:#2d6cdf,stroke:#1b3f85,color:#fff
style Services fill:#2f9e6e,stroke:#1c5f43,color:#fff
style Workers fill:#2d6cdf,stroke:#1b3f85,color:#fff
style DB fill:#6b5bd2,stroke:#3f3480,color:#fff
Eight projects in one solution, split by responsibility:
| Layer | Responsibility |
|---|---|
| API | HTTP surface — controllers, middleware, custom attributes |
| Client | Client-facing application |
| Services | Application services, operations, domain validators |
| Repositories | Data access behind the repository pattern |
| Data | EF Core context, entity models, migrations |
| Infrastructure | Cross-cutting concerns |
| Shared | Types shared across projects |
| Workers | Hosted background services |
Dependencies point inward. API and Workers depend on Services; Services on Repositories; Repositories on the data layer. Nothing in the inner layers reaches back out. The practical payoff is that business rules can be exercised without spinning up a web host.
Validation lives with the domain, not the controllers. Domain validators sit in the service layer, so the same rules apply whether a change arrives over HTTP or from a background worker. Putting validation in controllers would have meant duplicating it in the worker path — and the two copies drifting apart.
Two entry points, one core. The API and the workers are separate processes with separate lifetimes, but both compose the same service layer. Scheduled and interactive work therefore cannot disagree about what is legal.
Schema changes are migrations, not scripts. EF Core migrations are checked in alongside the models, so the schema moves with the code that depends on it.
Cross-cutting concerns are middleware and attributes, not conditionals scattered through controllers.
C# / .NET · ASP.NET Core · Entity Framework Core · hosted background services · relational database
This describes the architecture and the reasoning behind it, which is the part I can share. Specifics about the client, the domain rules, the schema, and the deployment are covered by the NDA and are deliberately absent.
The repository is private under NDA and cannot be shared. I am happy to talk through the design, the trade-offs, and the parts I would build differently now.