From coding-standards
Review .NET / C# code against team conventions — messaging architecture, API design, testing, dependency management, and analyser compliance. Auto-invoked when reviewing .cs files.
How this skill is triggered — by the user, by Claude, or both
Slash command
/coding-standards:review-dotnetThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Review .NET and C# code against team architectural patterns and conventions. This methodology covers messaging architecture, API design, testing, dependency management, and analyser compliance.
Review .NET and C# code against team architectural patterns and conventions. This methodology covers messaging architecture, API design, testing, dependency management, and analyser compliance.
Execute all seven passes. Every finding requires file, line, and evidence.
The foundational rule: every message handler performs exactly one unit of work. No loops with heavy inline logic. No handlers that orchestrate multiple unrelated operations.
Handler scan — find all message handlers in changed files:
grep -rn 'IHandleMessages\|ICommandHandler\|IEventHandler\|Handle\s*(' --include='*.cs' [changed files]
Loop detection in handlers — grep for foreach, for (, while (, Parallel.For, .Select( inside handler methods. Each loop that performs I/O, database access, or publishes messages is a finding:
foreach (var item in items) { await _repository.Save(item); }Handler responsibility — read each handler. It should:
Side effects in handlers — handlers should not send emails, call external APIs, or write to multiple aggregates. Those belong in separate handlers triggered by domain events.
[AggregateHandler] usage — aggregates that handle commands must use the [AggregateHandler] attribute with cascading returns, not manual event publishing:
grep -rn 'AggregateHandler\|\.Publish\|\.Send\(' --include='*.cs' [changed files]
If a handler inside an aggregate calls _bus.Publish() or _mediator.Send() manually, that is a finding. Use cascading return types instead — the infrastructure handles event dispatch.
Return types — aggregate handlers return domain events as their result. The framework publishes them. Verify handlers return the event, not void or Task.
Idempotency guards on creation handlers — handlers that create aggregates from coordination events (e.g., WorkItemTriggered → create WorkItem) must check whether the aggregate already exists before calling create. Event replay after a partial write will re-run the handler, and a second create for the same ID produces a duplicate-version conflict in the event store:
var item = new WorkItem(command.Id); await repository.Save(item);AggregateNotFound, proceed with creation.Event immutability — domain events must be immutable records or classes with init-only properties. Grep for set; in event classes:
grep -rn 'set;' --include='*.cs' [event file paths]
URL structure — API endpoints must reflect entity ownership hierarchically:
/api/organisations/{orgId}/projects/{projectId}/tasks/{taskId}/api/tasks/{taskId} (loses the ownership chain)
Read controller route attributes and verify the hierarchy matches the domain model.Static LoadAsync for preconditions — endpoints that need to validate entity existence or permissions before executing should use the static LoadAsync pattern:
public static async Task<IResult> LoadAsync(Guid id, IRepository repo)
This separates precondition loading from business logic. If a controller action starts with 5 lines of "fetch and check if null" logic, that belongs in LoadAsync.
List endpoints — pagination, sort, filter — every list endpoint must support:
Grep for list endpoints (methods returning collections) and verify these parameters exist:
grep -rn 'IEnumerable\|IList\|List<\|IQueryable\|Task<.*\[\]>' --include='*.cs' [changed files]
If a list endpoint loads all records and filters in memory (.ToList() followed by .Where()), that is a critical finding.
Consistent response shapes — all endpoints should return consistent wrapper types. No raw primitives as responses.
Constructor injection only — dependencies come through the constructor, never through property injection, service locator, or static helpers:
grep -rn 'ServiceLocator\|GetService\|Resolve<\|\.GetRequiredService' --include='*.cs' [changed files]
Every hit is a finding. Use constructor injection with interfaces.
External dependencies behind interfaces — every external system (database, HTTP client, file system, clock, message bus) must be accessed through an interface. Direct usage of HttpClient, DateTime.Now, File.ReadAllText is a finding:
grep -rn 'DateTime\.Now\|DateTime\.UtcNow\|File\.\|Directory\.' --include='*.cs' [changed files]
Use IDateTimeProvider, IFileSystem, etc. This enables testing.
Central package management — verify the solution uses Directory.Packages.props for NuGet versions. Individual .csproj files should not specify package versions:
grep -rn 'Version=' --include='*.csproj' [changed files]
Package versions in .csproj files are a finding (they should use VersionOverride only in exceptional cases).
Session lifecycle — database sessions / units of work must be managed by infrastructure, not by handlers. Handlers should not call SaveChanges(), Commit(), or Dispose() on sessions directly. The pipeline handles that.
Transaction boundaries — if a handler needs a transaction broader than the default, it must be explicit and documented. Grep for BeginTransaction, TransactionScope:
grep -rn 'BeginTransaction\|TransactionScope' --include='*.cs' [changed files]
Each hit requires a comment explaining why the default session management is insufficient.
Warnings-as-errors — verify the project treats analyser warnings as errors. Check .csproj or Directory.Build.props:
grep -rn 'TreatWarningsAsErrors\|WarningsAsErrors' --include='*.csproj' --include='*.props'
If TreatWarningsAsErrors is not true, that is a finding.
Suppression audit — grep for all analyser suppressions:
grep -rn '#pragma warning disable\|SuppressMessage\|GlobalSuppressions' --include='*.cs' [changed files]
Every suppression must have:
Nullable reference types — verify <Nullable>enable</Nullable> is set. Grep for null! (null-forgiving operator):
grep -rn 'null!' --include='*.cs' [changed files]
Each null! is a finding unless it is in a test fixture or deserialization constructor with a comment.
BDD naming — test methods must use BDD-style names describing the scenario:
WhenCreatingUserWithDuplicateEmail_ShouldReturnConflictTestCreateUser, CreateUser_Test, Test1grep -rn '\[Fact\]\|\[Theory\]\|\[Test\]' --include='*.cs' -A1 [changed files]
Read the method name on the line after each attribute.
Test structure — each test follows Arrange/Act/Assert (or Given/When/Then). Tests with multiple Act steps test too much — split them.
No test logic — tests should not contain if, switch, try/catch, or loops. A test that branches is testing multiple things.
Integration over mocking — prefer integration tests with real dependencies (in-memory database, test bus) over unit tests that mock everything. Tests that mock the thing being tested are always wrong.
### [SEVERITY] [Pass]: [Short description]
**File:** `path/to/File.cs:42`
**Evidence:** [code or grep output]
**Standard:** [which rule is violated]
**Fix:** [concrete code change or architectural suggestion]
## .NET Review
### Summary
- Files reviewed: N
- Message architecture: X findings
- Aggregate patterns: X findings
- API design: X findings
- Dependencies: X findings
- Sessions: X findings
- Analysers: X findings
- Testing: X findings
### Findings
[grouped by severity: critical, important, suggestion]
### Clean Areas
[what was done well]
If all checks pass: "No findings. .NET review complete — all changed files comply with team conventions." Do not fabricate issues.
/coding-standards:review-standards — cross-cutting quality and writing style checks that apply to all languages. Run alongside this review./coding-standards:review-git — commit message and PR conventions. Run when reviewing a PR.npx claudepluginhub hpsgd/turtlestack --plugin coding-standardsGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.