The server side of DevCoreApp is split into multiple projects, each with a clear responsibility. This separation keeps dependencies explicit, improves testability, and allows teams to work on different layers independently.
/src/Server/
├── Api/ # HTTP host: /api controllers, SignalR, health, /setup; hosts the WASM clients
├── Services/ # Business logic, authentication, notifications, background jobs
├── Database/
│ ├── Core/ # Entities, queries, decorators, EF configuration
│ ├── Postgres/ # PostgreSQL provider and migrations
│ └── SqlServer/ # SQL Server provider and migrations
├── Email/
│ ├── Processor/ # Email abstractions and interfaces
│ ├── MailKit/ # MailKit-based email implementation
│ ├── Smtp/ # SMTP email implementation
│ └── SendGrid/ # SendGrid email implementation
└── Storage/
├── Processor/ # File storage abstractions and interfaces
├── Local/ # Local disk storage implementation
└── S3/ # AWS S3 storage implementation
The server follows a layered architecture with strict dependency rules:
WASM clients → /api controllers → Services → Queries/Repository → Database
The entry point of the application. Hosts the /api controllers, the SignalR hub, health checks and the first-run /setup page, and serves the Blazor WebAssembly clients (src/Client: Desktop at /, Mobile at /mobile). Controllers derive from ApiControllerBase and make one service call per action through HandleServiceAsync(), which maps results and exceptions to the API wire contract. This project never accesses the database directly — all data access goes through Services.
Contains all business logic. Services inherit from BaseService, are annotated with [BlazorService] for automatic DI registration, and return ServiceActionResult<T>. They access data through query classes obtained from IQueryRepository, never through DbContext directly.
The Core project defines entities, EF configuration, query classes, and decorators. Postgres and SqlServer projects contain provider-specific migrations and configuration. Query classes implement IModelQuery<T,D> and support pagination, search, and sorting. Decorators provide ToView() and ToRecord() extension methods for mapping between entities and ViewModels.
Provider-based email sending. The Processor project defines the abstractions; MailKit, Smtp, and SendGrid are swappable implementations. Emails are queued via IBackgroundWorker and sent asynchronously.
Provider-based file storage. The Processor project defines the abstractions; Local and S3 are swappable implementations. File metadata is stored in the FileRecords table; physical files are managed by the active provider.
- Services own business logic — pages and controllers are thin, services are where decisions happen.
- Query classes own data access — services never call
DbContextdirectly. - Decorators own mapping —
ToView()converts entities to ViewModels,ToRecord()maps DTOs back onto entities. - Providers are swappable — database, email, and storage providers can be changed without touching business logic.
- Organization-scoped data — business tables include
OrganizationIdand EF global query filters enforce data isolation automatically.