Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Design and implement Minimal APIs in ASP.NET Core using handler-first endpoints, route groups, filters, and lightweight composition suited to modern .NET services. USE FOR: building new HTTP APIs in ASP.NET Core; creating lightweight microservices; choosing between Minimal APIs and controllers. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation com
.claude/skills/managedcode-minimal-apis/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-12 | ✗→✓ | ▲ Improved | 69% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 52% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 39% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 74% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 74% | 0% |
| Use Minimal APIs | Use Controllers | |------------------|-----------------| | New projects | Existing MVC/API projects | | Microservices | Complex model binding | | Simple CRUD APIs | OData, JsonPatch | | Lightweight handlers | Heavy use of attributes | | .NET 8+ projects | Need [ApiController] features |
.WithOpenApi()dotnet/aspnetcore v10.0.11 is servicing; it does not change the Minimal API route-group, filter, or TypedResults model, but it updates the OpenAPI stack to 2.7.5. Re-run document-generation and contract snapshots after upgrading.aspnetcore-10.0 overview still routes lightweight HTTP APIs here. Use the dedicated Minimal API pages when exact OpenAPI, filter, or parameter-binding behavior matters.csharpvar app = builder.Build(); app.MapGet("/", () => "Hello World"); app.MapGet("/products/{id}", (int id) => Results.Ok(new { Id = id })); app.MapPost("/products", (Product product) => Results.Created($"/products/{product.Id}", product));
csharpapp.MapGet("/products/{id}", Results<Ok<Product>, NotFound> (int id, AppDb db) => { var product = db.Products.Find(id); return product is not null ? TypedResults.Ok(product) : TypedResults.NotFound(); });
csharpapp.MapGet("/products", async (IProductService service) => { return await service.GetAllAsync(); }); // Or with [FromServices] for clarity app.MapGet("/products", async ([FromServices] IProductService service) => await service.GetAllAsync());
csharpvar products = app.MapGroup("/api/products"); products.MapGet("/", GetAll); products.MapGet("/{id}", GetById); products.MapPost("/", Create); products.MapPut("/{id}", Update); products.MapDelete("/{id}", Delete);
csharpvar api = app.MapGroup("/api") .RequireAuthorization() .AddEndpointFilter<ValidationFilter>(); var products = api.MapGroup("/products") .WithTags("Products"); var orders = api.MapGroup("/orders") .WithTags("Orders") .RequireAuthorization("AdminOnly");
csharpapp.MapGet("/products/{id}", (int id) => Results.Ok(id)) .AddEndpointFilter(async (context, next) => { var id = context.GetArgument<int>(0); if (id <= 0) return Results.BadRequest("Invalid ID"); return await next(context); });
csharppublic class ValidationFilter<T> : IEndpointFilter where T : class { public async ValueTask<object?> InvokeAsync( EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var argument = context.Arguments .OfType<T>() .FirstOrDefault(); if (argument is null) return Results.BadRequest("Invalid request body"); var validator = context.HttpContext.RequestServices .GetService<IValidator<T>>(); if (validator is not null) { var result = await validator.ValidateAsync(argument); if (!result.IsValid) return Results.ValidationProblem(result.ToDictionary()); } return await next(context); } } // Usage products.MapPost("/", Create) .AddEndpointFilter<ValidationFilter<CreateProductRequest>>();
csharp// All endpoints inherit filters from root group var root = app.MapGroup("") .AddEndpointFilter<LoggingFilter>() .AddEndpointFilter<ErrorHandlingFilter>(); root.MapGet("/health", () => Results.Ok()); root.MapGroup("/api/products").MapGet("/", GetProducts);
csharp// ProductEndpoints.cs public static class ProductEndpoints { public static RouteGroupBuilder MapProductEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/products") .WithTags("Products"); group.MapGet("/", GetAll); group.MapGet("/{id}", GetById); group.MapPost("/", Create); return group; } private static async Task<Ok<List<Product>>> GetAll(IProductService service) => TypedResults.Ok(await service.GetAllAsync()); private static async Task<Results<Ok<Product>, NotFound>> GetById( int id, IProductService service) { var product = await service.GetByIdAsync(id); return product is not null ? TypedResults.Ok(product) : TypedResults.NotFound(); } private static async Task<Created<Product>> Create( CreateProductRequest request, IProductService service) { var product = await service.CreateAsync(request); return TypedResults.Created($"/api/products/{product.Id}", product); } } // Program.cs app.MapProductEndpoints(); app.MapOrderEndpoints();
csharp// Separate from domain models public record CreateProductRequest(string Name, decimal Price); public record UpdateProductRequest(string Name, decimal Price); public record ProductResponse(int Id, string Name, decimal Price); // Don't expose domain entities directly app.MapPost("/products", (CreateProductRequest request, IMapper mapper) => { var product = mapper.Map<Product>(request); // ... return TypedResults.Created($"/products/{product.Id}", mapper.Map<ProductResponse>(product)); });
| Anti-Pattern | Why It's Bad | Better Approach | |--------------|--------------|-----------------| | Everything in Program.cs | Unmaintainable | Use extension methods | | No route groups | Repetitive config | Group related endpoints | | Manual validation | Error-prone | Use filters + FluentValidation | | Exposing entities | Tight coupling | Use DTOs | | No TypedResults | No compile-time checks | Use TypedResults | | Ignoring OpenAPI | No documentation | Add .WithOpenApi() |
csharpbuilder.Services.AddOpenApi(); app.MapOpenApi(); // Serves OpenAPI spec app.MapGet("/products", GetProducts) .WithName("GetProducts") .WithSummary("Get all products") .WithDescription("Returns a list of all available products") .Produces<List<Product>>(StatusCodes.Status200OK) .ProducesProblem(StatusCodes.Status500InternalServerError);
Other measured skills in the registry, with their headline benchmark lift.