agentsclimarketplace

Design dotnet feature

Skill tunahanaliozturk/secure-dotnet-skills/skills/design-dotnet-feature

Aegis — 12 judgment-style agent skills for secure, production-grade .NET on Azure (security, design, performance, concurrency, observability). Works with Claude Code, Codex, Cursor, Gemini.

Install
npx -y skills add tunahanaliozturk/secure-dotnet-skills --skill design-dotnet-feature

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when designing a new backend feature in a .NET app — to shape boundaries, layering (Clean Architecture / CQRS), validation, and error handling before writing code.

SKILL.md

11.9 KB, as published. Nobody here has run it

Design a .NET Feature

Directs the agent to think through a feature's structure — command/query split, layer boundaries, contracts, validation strategy, error model, and build order — before any code is written, producing a concrete design that a developer can implement slice by slice without revisiting architecture mid-flight.

When to use

  • A new command or query is being added to an ASP.NET Core app and the developer needs to settle layering, contracts, and error handling before opening a code file.
  • A feature touches money, state transitions, or external systems where idempotency and aggregate boundaries matter.
  • The team is debating FluentValidation vs DataAnnotations, Result<T> vs exceptions, or vertical slices vs a traditional N-tier layout.
  • A code review reveals that business logic leaked into a controller or that a handler is pulling EF entities directly into the API response.

Process

  1. Clarify the use case, inputs, and invariants. Name the actor, the trigger, the required preconditions (e.g., license key must be unused and valid), and the postcondition (what is true after success). Resolve ambiguities now — an unclear invariant becomes a production bug later.
  2. Choose the boundaries and classify the operation. Decide which domain concepts are involved and which Clean Architecture layer owns the logic (domain / application / infrastructure / API). Classify the operation as a command (mutates state) or a query (returns data, no side effects). One use case = one IRequest<TResponse> handler; resist the urge to reuse a handler for a similar-but-different flow.
  3. Define the contracts — request, response, and domain events. Write the request DTO (the MediatR IRequest<TResponse> or minimal-API delegate parameter), the success response DTO, and any domain event raised by the aggregate. Keep DTOs in the Application layer; never expose EF entities as response types.
  4. Choose a validation strategy. Decide whether validation lives in a FluentValidation.IValidator<TRequest> registered as a MediatR IPipelineBehavior<TRequest, TResponse> (preferred for complex rules and reuse) or as [Required] / [StringLength] DataAnnotations auto-validated by the model binder (acceptable for simple, framework-owned DTOs). Document which rules belong to domain invariants inside the aggregate vs. input sanitization that belongs in the validator.
  5. Choose an error model: expected failures vs. exceptional failures. Use Result<T> (or a discriminated union) for foreseeable business failures (key already redeemed, key not found, quota exceeded) — these are domain outcomes, not exceptions. Reserve C# exceptions for truly exceptional conditions (DB connectivity failure, programming errors). At the API edge, a ProblemDetails middleware or minimal-API TypedResults.Problem(...) maps Result failure cases to RFC 7807 responses with the appropriate status and detail fields.
  6. Note cross-cutting concerns. Auth: which policy guards this endpoint ([Authorize(Policy = "…")])? Logging: what correlation context should be enriched (using (_logger.BeginScope(new { Command = request.GetType().Name }))? Idempotency: for any unsafe operation (payment, license activation, email send), plan an idempotency key — accept it as a header (Idempotency-Key: <uuid>) or request field, persist the first response keyed to it, and return the cached response for duplicates without re-executing the handler. DI: every external dependency (email gateway, license store, time provider) must be behind an interface registered in Program.cs or a module; the handler depends on the abstraction so it can be tested with a fake or in-memory substitute.
  7. List the slices to build, in order. Enumerate the vertical slices in implementation sequence: (a) domain entity / aggregate changes, (b) application-layer request + handler + validator, (c) infrastructure registration (EF DbSet, repository, external client), (d) API endpoint (controller action or minimal-API route group), (e) integration test covering the happy path and each Result failure branch, (f) idempotency test (duplicate request returns same response, no double-write).

.NET / Azure checks

  • Vertical slice and MediatR handler shape. Each use case maps to exactly one IRequest<Result<TResponse>> (command) or IRequest<TResponse> (query) and one IRequestHandler<,> implementation. The handler is thin: it calls the domain aggregate or service, persists via EF Core, and returns a Result. No business logic, no EF queries, no mapping inside the controller action — the controller's only job is to send the request to MediatR and translate the Result into an HTTP response.
  • Validation placement — FluentValidation pipeline behavior vs DataAnnotations. Register FluentValidation.AspNetCore and a ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> that calls IValidator<TRequest>.ValidateAsync before the handler runs; return a ValidationResult failure (mapped to HTTP 422 with a ProblemDetails body containing errors) if validation fails. Use DataAnnotations ([Required], [MaxLength]) only on shallow input models where framework-default model binding is sufficient. Never mix domain invariant checks (uniqueness, business rules) into validators — those belong inside the aggregate and surface as Result.Failure(...).
  • Error model — Result<T> for expected outcomes, exceptions for exceptional ones. Define a Result<T> type (or use a library such as ErrorOr, LanguageExt, or Ardalis.Result) with a Value, an IsSuccess flag, and an Error descriptor that carries a code and human-readable detail. Handlers return Result.Success(response) or Result.Failure(Error.NotFound("LicenseKey.NotFound", "License key does not exist")). At the API edge, a minimal-API handler or an action filter translates each error code to the correct HTTP status: NotFound → 404, Conflict → 409, Validation → 422 — all wrapped in RFC 7807 ProblemDetails with type, title, status, and detail populated. Never throw new NotFoundException(...) for a lookup miss; that is a domain outcome, not an exception.
  • ProblemDetails at the API edge. Register builder.Services.AddProblemDetails() and app.UseExceptionHandler() to handle truly exceptional failures (unhandled exceptions) with a safe, non-verbose ProblemDetails response that does not leak stack traces or internal messages in non-Development environments. For expected Result failures, map manually or via a ResultExtensions.ToProblemDetails(this Result result) helper that sets status, type (a stable URI like https://errors.example.com/license-key/not-found), and detail from the Error descriptor.
  • EF Core aggregate and transaction boundary. Align the SaveChangesAsync() call to the command boundary: one handler = one SaveChangesAsync() at the end. If the command touches multiple aggregates that must be consistent (e.g., marking the license key redeemed and creating the subscription in the same transaction), either co-locate them in the same DbContext call or introduce a domain event dispatched after SaveChangesAsync() and processed in a separate transaction (eventual consistency). Do not call SaveChangesAsync() inside a loop, inside a domain method, or in a repository method called multiple times per handler — batch all writes into one call.
  • Idempotency for unsafe operations. For any command that produces a side effect (activates a license, charges a payment, sends an email, creates a resource), design idempotency from day one. Accept an Idempotency-Key header (UUID v4) via an [FromHeader] parameter or a middleware. Store the first completed response (status code + body) in a durable cache keyed by (userId, idempotencyKey) — a dedicated EF IdempotencyRecord table or Redis are both valid. On a duplicate request (same key, same user), return the stored response without re-executing the handler. Set a sensible TTL (24 hours is common). Log repeated key usage so operations teams can detect client retry storms.
  • DI registration and testing seams. Register the handler via builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(...)). Every external dependency (email sender, license validator, time provider, outbound HTTP client) must be behind an interface registered with an appropriate lifetime (IEmailSenderScoped, ILicenseKeyStoreScoped, ISystemClockSingleton). Use IHttpClientFactory for outbound HTTP (never new HttpClient()). In integration tests, replace the real infrastructure with fakes registered in the test's WebApplicationFactory.ConfigureTestServices; the handler is exercised end-to-end without hitting real external systems.

Red flags

SignalWhy it matters
Business logic directly inside a controller action (if (key.ExpiresAt < DateTime.UtcNow) return BadRequest(...))Controllers own HTTP plumbing, not domain rules. The logic is untestable without an HTTP stack, duplicated across overloads, and invisible to MediatR pipeline behaviors (validation, logging, retry).
Handler returns an EF entity type to the API (Task<LicenseKey> where LicenseKey is the EF model)EF entity types carry navigation properties, change-tracker state, and potentially circular references. Returning them serializes internal DB schema to the API contract, creates over-fetching, and couples API versioning to the EF model. Map to a DTO.
throw new NotFoundException("Key not found") in a handler for an ordinary lookup missA missing record is a foreseeable business outcome, not an exceptional condition. Throwing here forces the middleware to catch and translate it, adds stack-trace overhead on every miss, and makes the handler's failure modes implicit rather than typed. Use Result.Failure(Error.NotFound(...)).
No idempotency on a handler that activates a license, charges a card, or sends an emailNetwork retries and client double-clicks re-execute the side effect. A license gets activated twice, a customer charged twice, or a welcome email sent twice. Idempotency must be designed in; it cannot be retrofitted safely after go-live.
SaveChangesAsync() called inside a domain method or inside a loop in a handlerMultiple partial saves break atomicity — a failure mid-loop leaves the DB in an inconsistent state. One handler = one SaveChangesAsync() at the end.
FluentValidation validator checking a uniqueness rule via a direct _dbContext.Set<T>().AnyAsync(...) queryUniqueness is a domain invariant that must be enforced inside a transaction alongside the write. A validator runs before the handler — the record can be inserted between validation and the handler's write (TOCTOU race). Enforce uniqueness via a unique index + catch DbUpdateException, or check-and-insert in the same transaction inside the handler.
A single IRequestHandler handles both "create" and "update" based on a flag (bool isUpdate)Two operations with different invariants, validations, and failure modes sharing one handler. The conditional branching grows; tests become combinatorial. One use case, one handler.
new HttpClient() constructed inside a handler or serviceEach call creates a new socket, exhausting ephemeral ports under load. Inject IHttpClientFactory and call _factory.CreateClient("named-client"); the factory manages pooling and lifetime.

Example

See examples/design-dotnet-feature/.

Related skills

  • api-contract-review — use to review the HTTP contract of endpoints produced by the feature design.
  • solid-review — use to review the feature design against SOLID principles and Clean Architecture boundaries.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.