Guide for building ASP.NET Core Minimal APIs with OpenAPI/Swagger integration in .NET 10
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.
Minimal APIs are a simplified way to create HTTP APIs in ASP.NET Core without controllers. They provide:
Use Minimal APIs when:
Use MVC Controllers when:
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:
WebApplication.CreateBuilder(args) - creates builderbuilder.Services - dependency injectionapp.Build() - builds applicationapp.Map*() - defines endpointsapp.Run() - starts serverSimple 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();
});
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());
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:
.WithName() for OpenAPIOrganize 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));
var api = app.MapGroup("/api/v1");
api.MapGet("/users", () => "All users");
api.MapGet("/products", () => "All products");
var admin = app.MapGroup("/admin")
.RequireAuthorization("AdminPolicy");
admin.MapGet("/users", () => "Admin: All users");
admin.MapDelete("/users/{id}", (int id) => Results.NoContent());
// 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));
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
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");
app.MapGet("/users/{id}", (int id) => Results.Ok(new User()))
.Produces<User>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
Prefer:
app.MapGet("/users/{id}", (int id) =>
Results.Ok(user)
// or Results.NotFound()
);
Avoid:
app.MapGet("/users/{id}", (int id) => user); // Implicit 200 OK
app.MapGet("/users", () => "All users")
.WithName("GetAllUsers");
Required for:
var users = app.MapGroup("/users").WithTags("Users");
users.MapGet("/", GetAllUsers).WithName("GetAllUsers");
users.MapGet("/{id}", GetUser).WithName("GetUser");
users.MapPost("/", CreateUser).WithName("CreateUser");
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);
}
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);
This project has ImplicitUsings disabled. Always include:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
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);
});
app.MapGet("/admin/users", () => "Admin users")
.RequireAuthorization("AdminPolicy");
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.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
.WithName("EndpointName")
.WithSummary("Short summary")
.WithDescription("Longer description")
.WithTags("Tag1", "Tag2")
.Produces<Type>(statusCode)
.RequireAuthorization(policy)
var group = app.MapGroup("/prefix");
group.MapGet("/path", handler);
(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
Minimal APIs are a stable feature as of .NET 6.0+. This project uses .NET 10 RC 2 patterns.