akires47

dotnet-vertical-slice

0
0
# Install this skill:
npx skills add akires47/agent-skills --skill "dotnet-vertical-slice"

Install specific skill from multi-skill repository

# Description

Create minimal APIs using vertical slice architecture. Use it when building APIs organized by feature rather than technical layers, with result pattern for error handling, and fluent validation pattern. No external packages required.

# SKILL.md


name: dotnet-vertical-slice
description: Create minimal APIs using vertical slice architecture. Use it when building APIs organized by feature rather than technical layers, with result pattern for error handling, and fluent validation pattern. No external packages required.


.NET 10 Vertical Slice Architecture

Organize code by feature, not by layer. Each feature is self-contained with its endpoint, request/response, validation, and handler in a single file.

Project Structure

src/
β”œβ”€β”€ Features/
β”‚   β”œβ”€β”€ Products/
β”‚   β”‚   β”œβ”€β”€ GetProduct.cs
β”‚   β”‚   β”œβ”€β”€ CreateProduct.cs
β”‚   β”‚   └── UpdateProduct.cs
β”‚   └── Orders/
β”‚       └── ...
β”œβ”€β”€ Shared/
β”‚   β”œβ”€β”€ Results/
β”‚   β”‚   β”œβ”€β”€ Result.cs
β”‚   β”‚   └── Error.cs
β”‚   └── Validation/
β”‚       └── ValidationResult.cs
β”œβ”€β”€ Entities/
└── Program.cs

Feature Slice Pattern

One file per operation containing everything needed:

// Features/Products/CreateProduct.cs
public static class CreateProduct
{
    public sealed record Request(string Name, decimal Price);
    public sealed record Response(int Id, string Name, decimal Price);

    public static async Task<Result<Response>> HandleAsync(
        Request request, AppDbContext db, CancellationToken ct)
    {
        var validation = Validate(request);
        if (!validation.IsValid)
            return validation.ToResult<Response>(null!);

        var product = ToEntity(request);
        db.Products.Add(product);
        await db.SaveChangesAsync(ct);

        return ToResponse(product);
    }

    private static ValidationResult Validate(Request request) =>
        ValidationExtensions.Validate()
            .NotEmpty(request.Name, "Name")
            .GreaterThan(request.Price, 0, "Price");

    private static Product ToEntity(Request request) =>
        new() { Name = request.Name, Price = request.Price };

    private static Response ToResponse(Product product) =>
        new(product.Id, product.Name, product.Price);

    public static void MapEndpoint(IEndpointRouteBuilder app) => app
        .MapPost("/api/products", async (Request request, AppDbContext db, CancellationToken ct) =>
            (await HandleAsync(request, db, ct)).ToCreatedResponse(r => $"/api/products/{r.Id}"))
        .WithName("CreateProduct")
        .WithTags("Products");
}

Core Principles

  1. Result pattern only - Never throw exceptions, return Result<T> or Result
  2. Static handlers - Use public static async Task<Result<T>> HandleAsync(...)
  3. Inline validation - Validate at handler start, return early on failure
  4. Inline mapping - Use private ToEntity() or ToResponse() methods within each feature
  5. Projections - Use .Select() for queries, avoid loading full entities

References

See detailed implementations in the references/ folder:

Guidelines

  • One feature = one file (endpoint + request/response + validation + handler)
  • Name files by operation: CreateProduct.cs, GetProducts.cs
  • Keep entities in shared folder (only cross-cutting concern)
  • Use [AsParameters] for query parameters
  • Group endpoints with .WithTags() for OpenAPI

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.