Architecture
The Wrapsfer API uses a small Clean Architecture-style layout: four projects under src/ with strictly inward-pointing dependencies, and two test projects under tests/. This page describes the layout that exists today and where future code should live.
Project Layout
src/
Wrapsfer.Api/
Controllers/ HTTP endpoints
Middleware/ Request pipeline middleware
Properties/ launchSettings.json
Program.cs Application startup and pipeline configuration
appsettings.json Base configuration
appsettings.Development.json
Wrapsfer.Api.csproj Microsoft.NET.Sdk.Web
Wrapsfer.Application/
Greetings/ Sample application service and contract
(IGreetingService, GreetingService, GreetingResponse)
DependencyInjection.cs AddApplication() service registrations
Wrapsfer.Domain/
Greetings/ Sample domain model (Greeting)
Wrapsfer.Infrastructure/
Options/ Configuration-bound options (AppOptions)
DependencyInjection.cs AddInfrastructure(IConfiguration) registrations
tests/
Wrapsfer.UnitTests/ Fast tests for domain/application behavior
Wrapsfer.IntegrationTests/ API-level tests using WebApplicationFactory<Program>
The solution file is Wrapsfer.slnx at the repo root.
Dependency Direction
Project references (verified in each .csproj):
| Project | References |
|---|---|
Wrapsfer.Api | Wrapsfer.Application, Wrapsfer.Infrastructure |
Wrapsfer.Infrastructure | Wrapsfer.Application, Wrapsfer.Domain |
Wrapsfer.Application | Wrapsfer.Domain |
Wrapsfer.Domain | (none) |
Wrapsfer.UnitTests | Wrapsfer.Application, Wrapsfer.Domain |
Wrapsfer.IntegrationTests | Wrapsfer.Api |
Visual summary:
Api ─► Application ─► Domain
│ ▲ ▲
▼ │ │
Infrastructure ──────────────┘
Dependencies always point inward: Domain depends on nothing; Application depends only on Domain; Infrastructure depends on Application and Domain; Api composes everything. There are no outward references and Domain has zero project or framework references that would tie it to ASP.NET Core, configuration, DI extensions, EF Core, etc.
Layer Responsibilities
Wrapsfer.Domain
The innermost layer. Contains domain models and rules that are independent of frameworks, persistence, and HTTP.
- Today: the
Greetingrecord (validatesnameis non-empty, exposesMessage). - Has no project or NuGet references —
Wrapsfer.Domain.csprojonly sets target framework, nullable, and implicit usings. - New domain rules and invariants belong here, expressed as types and methods that can be unit-tested without any framework setup.
Wrapsfer.Application
Use cases, contracts, and application-level orchestration. Talks to Domain; abstracts away from HTTP and infrastructure.
- Today:
IGreetingService/GreetingServiceand theGreetingResponseDTO;DependencyInjection.AddApplication()registersIGreetingServiceasScoped. - References Domain only (
ProjectReferencetoWrapsfer.Domain); pulls inMicrosoft.Extensions.DependencyInjection.Abstractionsso it can expose its ownAddApplication()extension. - New use-case interfaces and services belong here. Application code may depend on abstractions for infrastructure concerns (e.g. clocks, repositories) but should not reference concrete infrastructure types.
Wrapsfer.Infrastructure
Concrete adapters and configuration binding. Provides implementations for things the Application layer abstracts.
- Today:
Options/AppOptions(SectionName = "App",Name = "Wrapsfer API");DependencyInjection.AddInfrastructure(IConfiguration)registersTimeProvider.Systemas a singleton and bindsAppOptionsfrom theAppconfiguration section. - References
Wrapsfer.ApplicationandWrapsfer.Domain; pulls inMicrosoft.Extensions.Options.ConfigurationExtensionsforConfigure<T>(IConfigurationSection). - Persistence, external HTTP clients, message brokers, file system access, and other I/O-heavy adapters belong here when they are added.
Wrapsfer.Api
The composition root and HTTP surface. Wires everything together and exposes endpoints.
- Today:
Program.csregisters controllers, OpenAPI, health checks, CORS, then callsAddApplication()andAddInfrastructure(builder.Configuration); the pipeline isExceptionHandlingMiddleware→UseHttpsRedirection→UseCors("DefaultCors")→UseAuthorization→ health checks + controllers; OpenAPI is mapped only inDevelopment. Controllers/GreetingsControlleris the only controller today;Middleware/ExceptionHandlingMiddlewarereturnsapplication/problem+json500 for unhandled exceptions.- References
ApplicationandInfrastructure; usesMicrosoft.NET.Sdk.WebandMicrosoft.AspNetCore.OpenApi. - New HTTP endpoints, request/response shapes specific to HTTP, middleware, and pipeline configuration belong here.
Tests
Wrapsfer.UnitTestsreferencesApplicationandDomain. Use it for fast tests of domain rules and application services that do not need an HTTP host.Wrapsfer.IntegrationTestsreferencesApiand usesMicrosoft.AspNetCore.Mvc.Testing.WebApplicationFactory<Program>to spin up the in-memory host. Use it for endpoint behavior — status codes, headers, response bodies.
Where To Add New Behavior
A typical feature flows from inside out:
- Express the rule in
Wrapsfer.Domain. Add the type, invariants, and behavior as plain C#. No framework references. - Define the use case in
Wrapsfer.Application. Add an interface plus an implementation that orchestrates Domain. Register it inAddApplication(). If the use case needs an external dependency (clock, repository, HTTP client), depend on an abstraction defined in Application. - Provide the adapter in
Wrapsfer.Infrastructureonly if needed. Implement the abstraction and register it inAddInfrastructure(IConfiguration). Bind any newappsettingssection throughConfigure<T>. - Expose the behavior in
Wrapsfer.Api. Add a controller (or extend an existing one) that calls the application service. Keep request/response shapes specific to HTTP in this layer; let the application return its own DTOs. - Add unit tests in
Wrapsfer.UnitTestsfor domain and application behavior. - Add integration tests in
Wrapsfer.IntegrationTestsfor the HTTP behavior usingWebApplicationFactory<Program>.
Things To Keep Out Of Domain
- ASP.NET Core types (
HttpContext,IActionResult,ControllerBase, attributes like[FromQuery]). - Configuration / DI types (
IConfiguration,IServiceCollection,IOptions<T>). - Persistence concerns (EF Core entities,
DbContext, repository implementations). - Logging and telemetry frameworks.
If a domain operation needs the current time, an external service, or persistence, express the dependency as an abstraction owned by Domain or Application and implement it in Infrastructure. The current code follows this rule (e.g. GreetingService takes TimeProvider from DI rather than calling DateTimeOffset.UtcNow directly).
Things To Keep Out Of Application
- HTTP-specific types and attributes.
- Concrete infrastructure (database connections, file system access, third-party SDKs). Reference abstractions instead and let Infrastructure implement them.
See Also
- Configuration — how
AppOptions, CORS, and other settings are bound. - Testing — what
Wrapsfer.UnitTestsandWrapsfer.IntegrationTestscover. - Development Workflow — recommended flow for adding a feature, end to end.