dotnet-clean-architecture
Skill0 starsChanged 33 days agoMITView on GitHub
Build, extend, or review .NET 10 services that use Clean Architecture, CQRS (Mediator), EF Core and multi-tenant SaaS patterns. Use when scaffolding a new ASP.NET Core solution, adding a command, query or endpoint, adding tenant isolation, or auditing an existing .NET codebase for layering, tenancy, persistence and API bugs.
4 other projects use a version of this.
Adopted by
Most recently taken up by
- orrrrli/clawd-harnesstheir own version, first seen Oct 20, 2026
- orrrrli/ai-engineering-stacktheir own version, first seen Oct 11, 2026
- jjromero88/jjromero-skills-devtheir own version, first seen Jul 27, 2026
- ronnythedev/dotnet-clean-architecture-skillstheir own version, first seen May 20, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- ronnythedev/dotnet-clean-architecture-skills★ 73same name
- orrrrli/ai-engineering-stack★ 2same name
- orrrrli/clawd-harness★ 2same name
- jjromero88/jjromero-skills-dev★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repositoryOne other file it gives its agents.
- CLAUDE.mdCLAUDE.md
The file
We split this skill into the rules it gives an agent
12 rules. 0 are common, 4 are distinctive, and 8 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
4
Distinctive
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 45
Each layer registers itself: Every project exposes one AddX() extension (AddApplication, AddPersistence, AddInfrastructure).
- Very specific: line 78
Application returns
Result<T>, never HTTP concepts: No status codes in handlers. - Very specific: line 99
Keys are assigned by EF value generation: when the entity is added to the context.
- Very specific: line 110
PostgreSQL: use the xmin system column as the concurrency token.
- Distinctive: line 111
Startup never migrates in production: Use a migration bundle or a deployment job.
- Very specific: line 148
Create the layout above. Wire AddX() methods and both hosts.
- Very specific: line 149
Add the cross-cutting baseline: Result/Error, validation behavior, ProblemDetails, interceptors, named filters, fallback auth policy, …
- Distinctive: line 152
Add the architecture tests and the tenant-isolation test before any feature.
- Very specific: line 156
Create one folder per use case: Application/<Feature>/<UseCase>/ holding the command or query, its handler and its validator.
- Distinctive: line 159
Add any uniqueness as a filtered unique index in the entity configuration.
- Very specific: line 160
Add the endpoint to the feature's endpoint group.
- Distinctive: line 162
Add an integration test for the happy path, one failure path, and tenant isolation if the entity is tenant-owned.
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
.NET Clean Architecture (CQRS, EF Core, multi-tenant)
Opinionated guidance for building production .NET 10 services with Clean Architecture and CQRS. Every rule below exists because its absence caused a real bug in a real codebase.
Targets .NET 10, EF Core 10 and C# 14 (released November 2025; .NET 10 and EF Core 10 are LTS). Features marked "EF Core 10" do not exist in earlier versions.
The default database provider is SQL Server. Where a rule differs on PostgreSQL, a short PostgreSQL: note says how.
When to use
- Scaffolding a new service or solution.
- Adding a feature slice: command or query, handler, validator, endpoint, tests.
- Adding or reviewing multi-tenancy, soft delete, auditing or concurrency.
- Auditing an existing ASP.NET Core codebase and producing a findings report.
When NOT to use
- Small CRUD apps, prototypes or scripts. A single project with minimal APIs is the better choice there; say so instead of applying this structure.
- Restructuring an existing codebase that deliberately uses another architecture (for example vertical slices). Audit it on its own terms; do not rewrite it into this layout unless the user asks.
- Non-.NET stacks.
Non-negotiable rules
Apply these by default. Deviate only when the user asks, and state the trade-off.
Architecture
- Dependencies point inward. Domain references nothing. Application references Domain (and Contracts if it publishes integration events). Persistence and Infrastructure reference Application. Only hosts (Api, Worker) reference Persistence and Infrastructure. Hosts never reference each other.
- Each layer registers itself. Every project exposes one
AddX()extension (AddApplication,AddPersistence,AddInfrastructure). Hosts compose these methods and never duplicate the registrations inside them; a host calls only the ones it needs. Assemblies are located withtypeof(Marker).Assembly, neverAssembly.Load("Name").
Multi-tenancy
Rules 4–6 assume a shared database with a TenantId column on every
tenant-owned table. Database-per-tenant is out of scope for v1.
- Tenant identity comes from a trusted source, set by the host.
Application defines the
ITenantContextport; each host sets it. In the Api: middleware reads a claim on the authenticated principal, afterUseAuthentication(). A tenant header is accepted only if it matches the claim or the caller holds an explicit impersonation permission; every impersonated request is audit-logged with both the real caller and the target tenant. In the Worker: a consumer filter reads the message envelope. Infrastructure and Persistence never touchHttpContext. - Tenant and soft delete are separate named query filters (EF Core 10),
TenantandSoftDelete. With no resolved tenant, the tenant filter matches nothing (fail closed). Never callIgnoreQueryFilters()without filter names on tenant-owned data; system and platform-admin paths opt out withIgnoreQueryFilters(["Tenant"])inside an explicit, audited scope. - Tenant writes are guarded. The interceptor throws when a tenant-owned
entity is saved with no resolved tenant (never default to
Guid.Empty) and when an entity'sTenantIddiffers from the current tenant. - Uniqueness is tenant-scoped and soft-delete aware: a unique index on
(TenantId, Key)filtered on[IsDeleted] = 0. Validator checks are a courtesy message, not the guarantee. PostgreSQL: filter on"IsDeleted" = false.
Application and API
7. Application returns Result<T>, never HTTP concepts. No status codes in
handlers. Endpoints map Error.Type to ProblemDetails (RFC 9457) through
one shared helper that returns TypedResults. One error shape for the whole
API.
8. Secure by default. Configure a fallback authorization policy that
requires an authenticated user. Opt out explicitly with .AllowAnonymous()
only on endpoints that must be public, including liveness and readiness
health checks. Never call .AllowAnonymous() on a route group (or put
[AllowAnonymous] on a controller) that has protected endpoints; it wins
over every RequireAuthorization() or [Authorize] beneath it.
9. No unbounded reads. Lists take a clamped PageRequest and end their
sort with a unique tiebreaker (ThenBy(x => x.Id)). Queries project with
Select into DTOs.
10. State changes go through aggregate methods (Order.Create,
order.Confirm()). No mapper writes into entities. PUT replaces fully;
partial updates are an explicit PATCH.
Persistence
11. Cross-cutting fields are stamped by interceptors, not handlers:
tenant, audit, soft delete, domain-event-to-outbox (v2). Use
TimeProvider, not DateTime.UtcNow.
Keys are assigned by EF value generation when the entity is added to
the context. Domain code never sets Id; anything that needs it (domain
events, outbox, responses) reads it after Add, in practice at
SaveChanges. On SQL Server EF's SequentialGuidValueGenerator produces
sequential GUIDs suited to uniqueidentifier clustered indexes.
PostgreSQL: Npgsql 9.0+ generates UUIDv7 keys by default; keep the
provider default.
12. Optimistic concurrency reaches the client. Expose the row version
(rowversion) as an ETag; require If-Match on PUT, PATCH and
DELETE. Missing If-Match returns 428 Precondition Required; a stale one
returns 412 Precondition Failed.
PostgreSQL: use the xmin system column as the concurrency token.
13. Startup never migrates in production. Use a migration bundle or a
deployment job. Auto-migrate on startup is allowed only in Development, and
the code path throws if it is ever reached in any other environment.
Testing
14. Handlers that touch the database are tested against a real database
(Testcontainers). Never mock DbSet. Every tenant-owned entity is covered
by the tenant-isolation test template.
Solution layout
Orders.slnx
global.json pin SDK 10.0.x
Directory.Build.props net10.0, Nullable, TreatWarningsAsErrors, analyzers
Directory.Packages.props central package management
src/
Orders.Domain aggregates, value objects, domain events, errors
Orders.Application use-case slices, ports (incl. ITenantContext), behaviors, Result
Orders.Contracts integration events, public DTOs (no deps)
Orders.Persistence DbContext, configurations, interceptors, migrations
Orders.Infrastructure messaging, external clients
Orders.Api endpoint groups, tenant middleware, composition root
Orders.Worker consumers, tenant from envelope, outbox relay (v2), composition root
tests/
Orders.UnitTests domain logic, validators, pure services
Orders.IntegrationTests handlers + API against real SQL (Testcontainers)
Orders.ArchitectureTests enforces rule 1 and naming conventionsFull details: references/solution-structure.md (planned).
Workflows
Scaffold a new solution
- Confirm the service name, database provider (SQL Server by default) and whether it is multi-tenant.
- Create the layout above. Wire
AddX()methods and both hosts. - Add the cross-cutting baseline:
Result/Error, validation behavior,ProblemDetails, interceptors, named filters, fallback auth policy, health checks (liveness and readiness, marked.AllowAnonymous()). - Add the architecture tests and the tenant-isolation test before any feature.
Add a feature slice
- Read
references/cqrs-slices.md(planned). - Create one folder per use case:
Application/<Feature>/<UseCase>/holding the command or query, its handler and its validator. - Change state through an aggregate method; add one if missing.
- Add any uniqueness as a filtered unique index in the entity configuration.
- Add the endpoint to the feature's endpoint group; map
ResulttoTypedResultswith the shared helper. - Add an integration test for the happy path, one failure path, and tenant isolation if the entity is tenant-owned.
Audit an existing codebase
- Read
references/audit-checklist.md(planned). - Inspect, in order: project references, composition roots, tenant resolution,
query filters and
SaveChanges, entity indexes, one full slice, error handling, endpoints/authorization, tests. - Report findings grouped by severity (Critical, High, Medium), each with the evidence (file and line), the impact, and the fix. Lead with security and data-integrity issues. Note what is done well.
Decision points
| Decision | Default | Alternative (when) |
| Database provider | SQL Server | PostgreSQL, following the PostgreSQL: notes |
| Endpoints | Minimal APIs, endpoint groups, TypedResults | Controllers, if the codebase already uses them |
| Mediator library | Mediator (source-generated, MIT) | MediatR, if already licensed: references/mediatr-alternative.md (planned) |
| Data access in handlers | IAppDbContext with DbSets | Repositories, for rich aggregates with complex loading |
| Domain model | Aggregates with intent methods | Anemic entities, for pure reference data |
| Errors | Result<T> + ProblemDetails | Exceptions only for truly exceptional failures |
| Mapping | Explicit (Select, factory methods) | Mapster, for large read-only DTOs |
| Paging | Offset with clamped PageRequest | Keyset, for large tables or infinite scroll |
| Test assertions | AwesomeAssertions (Apache 2.0) or Shouldly (BSD-3-Clause) | FluentAssertions 7.x if already pinned; never 8+ (commercial license) |
Reference index
Load only the file the current task needs. Files marked (planned) do not exist yet; rely on the rules above until they do.
| File | Load when |
references/solution-structure.md (planned) | Scaffolding, or fixing project references |
references/domain-modeling.md (planned) | Writing aggregates, value objects, domain events |
references/cqrs-slices.md (planned) | Adding any command or query |
references/pipeline-behaviors.md (planned) | Validation, logging, transaction behaviors |
references/error-handling.md (planned) | Result, Error, ProblemDetails mapping |
references/persistence-efcore.md (planned) | DbContext, configurations, interceptors, indexes, migrations |
references/multi-tenancy.md | Anything touching tenant resolution or tenant-owned data |
references/api-endpoints.md (planned) | Endpoints, versioning, OpenAPI, authorization |
references/testing.md (planned) | Unit, integration, architecture and tenant-isolation tests |
references/audit-checklist.md (planned) | Reviewing an existing codebase |
references/anti-patterns.md (planned) | Explaining why a pattern is rejected |
references/mediatr-alternative.md (planned) | The project uses or requires MediatR |
Naming conventions
| Item | Convention | Example |
| Command | <Verb><Noun>Command | CreateOrderCommand |
| Query | Get<Noun(s)>Query | GetOrdersQuery |
| Handler | <UseCase>Handler | CreateOrderHandler |
| Validator | <UseCase>Validator | CreateOrderValidator |
| Response DTO | <Noun>Response / <Noun>Summary | OrderResponse |
| Error codes | <Aggregate>.<Reason> | Order.NotFound |
| Entities | Domain name, no Entity suffix | Order, not OrderEntity |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

