Claude-skill-registry dotnet-minimal-apis

Guide for building ASP.NET Core Minimal APIs with OpenAPI/Swagger integration in .NET 10

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dotnet-minimal-apis" ~/.claude/skills/majiayu000-claude-skill-registry-dotnet-minimal-apis && rm -rf "$T"
manifest: skills/data/dotnet-minimal-apis/SKILL.md
source content

ASP.NET Core Minimal APIs

This skill provides guidance for building Minimal APIs in ASP.NET Core .NET 10. Minimal APIs provide a simplified approach to building HTTP APIs with minimal dependencies and boilerplate.

Table of Contents

  1. Overview
  2. Project Structure
  3. Basic Endpoints
  4. Route Groups
  5. Parameter Binding
  6. OpenAPI Integration
  7. Best Practices
  8. Quick Reference

Overview

What are Minimal APIs?

Minimal APIs are a simplified way to create HTTP APIs in ASP.NET Core without controllers. They provide:

  • Less code: No controllers, no attribute routing
  • Fast development: Define endpoints inline
  • Performance: Lower overhead than MVC controllers
  • Ideal for: Microservices, simple APIs, cloud-native apps

When to Use Minimal APIs

Use Minimal APIs when:

  • Building microservices
  • Creating simple REST APIs
  • Prototyping quickly
  • Prioritizing performance

Use MVC Controllers when:

  • Complex validation logic
  • Heavy use of filters/middleware per endpoint
  • Team prefers OOP patterns
  • Large enterprise applications

Project Structure

ClaudeStack.API Project

This repository includes

src/ClaudeStack.API
demonstrating minimal API patterns.

Current Program.cs structure:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddOpenApi();

var app = builder.Build();

// Configure middleware
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();

// Define endpoints
app.MapGet("/weatherforecast", () => { /* ... */ })
   .WithName("GetWeatherForecast");

app.Run();

Key components:

  1. WebApplication.CreateBuilder(args)
    - creates builder
  2. builder.Services
    - dependency injection
  3. app.Build()
    - builds application
  4. app.Map*()
    - defines endpoints
  5. app.Run()
    - starts server

Basic Endpoints

MapGet

Simple GET endpoint:

app.MapGet("/hello", () => "Hello World!");

With route parameters:

app.MapGet("/users/{id}", (int id) => $"User {id}");

With query parameters:

app.MapGet("/search", (string? query) => $"Searching for: {query}");

Async with return type:

app.MapGet("/users/{id}", async (int id, UserService service) =>
{
    var user = await service.GetUserAsync(id);
    return user is not null ? Results.Ok(user) : Results.NotFound();
});

MapPost, MapPut, MapDelete

app.MapPost("/users", (User user) => Results.Created($"/users/{user.Id}", user));
app.MapPut("/users/{id}", (int id, User user) => Results.NoContent());
app.MapDelete("/users/{id}", (int id) => Results.NoContent());

Example from Project

From

src/ClaudeStack.API/Program.cs
:

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast");

Key points:

  • Inline lambda
  • Returns typed data (serialized as JSON)
  • .WithName()
    for OpenAPI

Route Groups

Basic Route Group

Organize related endpoints:

var users = app.MapGroup("/users");

users.MapGet("/", () => "All users");
users.MapGet("/{id}", (int id) => $"User {id}");
users.MapPost("/", (User user) => Results.Created($"/users/{user.Id}", user));

Route Group with Prefix

var api = app.MapGroup("/api/v1");

api.MapGet("/users", () => "All users");
api.MapGet("/products", () => "All products");

Route Group with Filters

var admin = app.MapGroup("/admin")
    .RequireAuthorization("AdminPolicy");

admin.MapGet("/users", () => "Admin: All users");
admin.MapDelete("/users/{id}", (int id) => Results.NoContent());

Parameter Binding

Binding Examples

// Route parameters
app.MapGet("/users/{id}", (int id) => $"User {id}");

// Query string
app.MapGet("/search", (string? q, int page = 1) => $"{q}, Page {page}");

// Request body (JSON)
app.MapPost("/users", (User user) => Results.Created($"/users/{user.Id}", user));

// Dependency injection
app.MapGet("/users", (UserService service) => service.GetAllUsers());
// Register: builder.Services.AddScoped<UserService>();

// Multiple sources
app.MapPost("/users/{id}", (int id, User user, UserService service) =>
    service.UpdateUser(id, user));

OpenAPI Integration

Adding OpenAPI

This project uses OpenAPI (configured in Program.cs):

// Add service
builder.Services.AddOpenApi();

// Map OpenAPI endpoint (development only)
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

Access OpenAPI spec:

https://localhost:5001/openapi/v1.json

Endpoint Metadata

Naming endpoints:

app.MapGet("/users", () => "All users")
   .WithName("GetAllUsers");

Adding descriptions:

app.MapGet("/users/{id}", (int id) => $"User {id}")
   .WithName("GetUser")
   .WithSummary("Get user by ID")
   .WithDescription("Returns a single user by their unique identifier");

Adding tags:

app.MapGet("/users", () => "All users")
   .WithTags("Users");

app.MapGet("/products", () => "All products")
   .WithTags("Products");

Response Types

app.MapGet("/users/{id}", (int id) => Results.Ok(new User()))
   .Produces<User>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);

Best Practices

1. Use Typed Results

Prefer:

app.MapGet("/users/{id}", (int id) =>
    Results.Ok(user)
    // or Results.NotFound()
);

Avoid:

app.MapGet("/users/{id}", (int id) => user); // Implicit 200 OK

2. Name All Endpoints

app.MapGet("/users", () => "All users")
   .WithName("GetAllUsers");

Required for:

  • URL generation
  • OpenAPI documentation
  • Testing

3. Organize with Route Groups

var users = app.MapGroup("/users").WithTags("Users");
users.MapGet("/", GetAllUsers).WithName("GetAllUsers");
users.MapGet("/{id}", GetUser).WithName("GetUser");
users.MapPost("/", CreateUser).WithName("CreateUser");

4. Extract Handler Methods

Instead of inline:

app.MapGet("/users", () => { /* 50 lines */ });

Use local functions:

app.MapGet("/users", GetAllUsers);

static IResult GetAllUsers(UserService service)
{
    var users = service.GetAll();
    return Results.Ok(users);
}

5. Use Record Types

Perfect for DTOs:

record User(int Id, string Name, string Email);
record CreateUserRequest(string Name, string Email);

ClaudeStack.API uses this pattern:

record WeatherForecast(DateOnly Date, int TemperatureC, string Summary);

6. Explicit Using Statements

This project has ImplicitUsings disabled. Always include:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

7. Validate Input

app.MapPost("/users", (CreateUserRequest request) =>
{
    if (string.IsNullOrEmpty(request.Name))
        return Results.BadRequest("Name is required");

    if (string.IsNullOrEmpty(request.Email))
        return Results.BadRequest("Email is required");

    // Create user
    return Results.Created("/users/1", user);
});

8. Use Filters for Cross-Cutting Concerns

app.MapGet("/admin/users", () => "Admin users")
   .RequireAuthorization("AdminPolicy");

Quick Reference

Endpoint Methods

app.MapGet("/path", handler)       // GET
app.MapPost("/path", handler)      // POST
app.MapPut("/path", handler)       // PUT
app.MapPatch("/path", handler)     // PATCH
app.MapDelete("/path", handler)    // DELETE
app.MapMethods("/path", ["GET", "POST"], handler)  // Custom

Results Helpers

Results.Ok(value)                  // 200 OK
Results.Created("/path", value)    // 201 Created
Results.NoContent()                // 204 No Content
Results.BadRequest(error)          // 400 Bad Request
Results.NotFound()                 // 404 Not Found
Results.Problem(details)           // 500 Internal Server Error

Endpoint Configuration

.WithName("EndpointName")
.WithSummary("Short summary")
.WithDescription("Longer description")
.WithTags("Tag1", "Tag2")
.Produces<Type>(statusCode)
.RequireAuthorization(policy)

Route Groups

var group = app.MapGroup("/prefix");
group.MapGet("/path", handler);

Parameter Binding Sources

(int id)                    // Route parameter
(string? query)             // Query string
(User user)                 // Request body (JSON)
(HttpRequest request)       // HTTP request
(HttpContext context)       // HTTP context
(IService service)          // DI service

Related Skills

  • dotnet-cli-essentials: Running and building API projects
  • aspnet-configuration: Configuring appsettings for APIs
  • mstest-testing-platform: Testing minimal APIs

Additional Resources


Version Information

  • .NET: 10.0 RC 2
  • ASP.NET Core: 10.0.0-rc.2.25502.107
  • OpenAPI Package: Microsoft.AspNetCore.OpenApi 10.0.0-rc.2.25502.107

Minimal APIs are a stable feature as of .NET 6.0+. This project uses .NET 10 RC 2 patterns.