dotnet-vertical-slice

📁 akires47/agent-skills 📅 Jan 24, 2026
10
总安装量
10
周安装量
#29025
全站排名
安装命令
npx skills add https://github.com/akires47/agent-skills --skill dotnet-vertical-slice

Agent 安装分布

claude-code 8
opencode 7
cursor 6
antigravity 4
gemini-cli 4
windsurf 3

Skill 文档

.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
│   │   └── ProductMapper.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 = new Product { Name = request.Name, Price = request.Price };
        db.Products.Add(product);
        await db.SaveChangesAsync(ct);

        return new Response(product.Id, product.Name, product.Price);
    }

    private static ValidationResult Validate(Request request) =>
        ValidationExtensions.Validate()
            .NotEmpty(request.Name, "Name")
            .GreaterThan(request.Price, 0, "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. Manual mapping – Use extension methods in *Mapper.cs files
  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