$npx -y skills add managedcode/dotnet-skills --skill minimal-apisDesign 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 a
| 1 | # Minimal APIs |
| 2 | |
| 3 | ## Trigger On |
| 4 | |
| 5 | - building new HTTP APIs in ASP.NET Core |
| 6 | - creating lightweight microservices |
| 7 | - choosing between Minimal APIs and controllers |
| 8 | - organizing endpoints with route groups |
| 9 | - implementing validation and filters |
| 10 | |
| 11 | ## Documentation |
| 12 | |
| 13 | - [Minimal APIs Overview](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-10.0) |
| 14 | - [Minimal API Tutorial](https://learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-10.0) |
| 15 | - [Filters in Minimal APIs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/min-api-filters?view=aspnetcore-10.0) |
| 16 | - [OpenAPI Support](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview?view=aspnetcore-10.0) |
| 17 | - [Route Groups](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/route-handlers?view=aspnetcore-10.0#route-groups) |
| 18 | |
| 19 | ### References |
| 20 | |
| 21 | - [patterns.md](references/patterns.md) - detailed route groups, filters, TypedResults patterns, parameter binding, error handling, and testing |
| 22 | - [anti-patterns.md](references/anti-patterns.md) - common Minimal API mistakes to avoid |
| 23 | |
| 24 | ## When to Use Minimal APIs vs Controllers |
| 25 | |
| 26 | | Use Minimal APIs | Use Controllers | |
| 27 | |------------------|-----------------| |
| 28 | | New projects | Existing MVC/API projects | |
| 29 | | Microservices | Complex model binding | |
| 30 | | Simple CRUD APIs | OData, JsonPatch | |
| 31 | | Lightweight handlers | Heavy use of attributes | |
| 32 | | .NET 8+ projects | Need `[ApiController]` features | |
| 33 | |
| 34 | ## Workflow |
| 35 | |
| 36 | 1. **Define endpoints directly in Program.cs** (for small APIs) |
| 37 | 2. **Use route groups** for related endpoints |
| 38 | 3. **Move handlers to separate classes** as the API grows |
| 39 | 4. **Apply filters** for cross-cutting concerns |
| 40 | 5. **Use TypedResults** for type-safe responses |
| 41 | 6. **Generate OpenAPI docs** with `.WithOpenApi()` |
| 42 | |
| 43 | ## Current Upstream Notes |
| 44 | |
| 45 | - `dotnet/aspnetcore` `v10.0.10` is servicing; it does not change the Minimal API route-group/filter/TypedResults model, but it fixes nullable `DescriptionAttribute` handling and duplicate XML documentation IDs in OpenAPI generation. |
| 46 | - The July 2026 `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. |
| 47 | |
| 48 | ## Basic Patterns |
| 49 | |
| 50 | ### Simple Endpoints |
| 51 | ```csharp |
| 52 | var app = builder.Build(); |
| 53 | |
| 54 | app.MapGet("/", () => "Hello World"); |
| 55 | |
| 56 | app.MapGet("/products/{id}", (int id) => Results.Ok(new { Id = id })); |
| 57 | |
| 58 | app.MapPost("/products", (Product product) => Results.Created($"/products/{product.Id}", product)); |
| 59 | ``` |
| 60 | |
| 61 | ### TypedResults (Strongly-Typed) |
| 62 | ```csharp |
| 63 | app.MapGet("/products/{id}", Results<Ok<Product>, NotFound> (int id, AppDb db) => |
| 64 | { |
| 65 | var product = db.Products.Find(id); |
| 66 | return product is not null |
| 67 | ? TypedResults.Ok(product) |
| 68 | : TypedResults.NotFound(); |
| 69 | }); |
| 70 | ``` |
| 71 | |
| 72 | ### Dependency Injection |
| 73 | ```csharp |
| 74 | app.MapGet("/products", async (IProductService service) => |
| 75 | { |
| 76 | return await service.GetAllAsync(); |
| 77 | }); |
| 78 | |
| 79 | // Or with [FromServices] for clarity |
| 80 | app.MapGet("/products", async ([FromServices] IProductService service) => |
| 81 | await service.GetAllAsync()); |
| 82 | ``` |
| 83 | |
| 84 | ## Route Groups |
| 85 | |
| 86 | ### Basic Grouping |
| 87 | ```csharp |
| 88 | var products = app.MapGroup("/api/products"); |
| 89 | |
| 90 | products.MapGet("/", GetAll); |
| 91 | products.MapGet("/{id}", GetById); |
| 92 | products.MapPost("/", Create); |
| 93 | products.MapPut("/{id}", Update); |
| 94 | products.MapDelete("/{id}", Delete); |
| 95 | ``` |
| 96 | |
| 97 | ### Groups with Shared Configuration |
| 98 | ```csharp |
| 99 | var api = app.MapGroup("/api") |
| 100 | .RequireAuthorization() |
| 101 | .AddEndpointFilter<ValidationFilter>(); |
| 102 | |
| 103 | var products = api.MapGroup("/products") |
| 104 | .WithTags("Products"); |
| 105 | |
| 106 | var orders = api.MapGroup("/orders") |
| 107 | .WithTags("Orders") |
| 108 | .RequireAuthorization("AdminOnly"); |
| 109 | ``` |
| 110 | |
| 111 | ## Endpoint Filters |
| 112 | |
| 113 | ### Inline Filter |
| 114 | ```csharp |
| 115 | app.MapGet("/products/{id}", (int id) => Results.Ok(id)) |
| 116 | .AddEndpointFilter(async (context, next) => |
| 117 | { |
| 118 | var id = context.GetArgument<int>(0); |
| 119 | if (id <= 0) |
| 120 | return Results.BadRequest("Invalid ID"); |
| 121 | |
| 122 | return await next(context); |
| 123 | }); |
| 124 | ``` |
| 125 | |
| 126 | ### Class-Based Filter |
| 127 | ```csharp |
| 128 | public class ValidationFilter<T> : IEndpointFilter where T : class |
| 129 | { |
| 130 | public async ValueTask<object?> InvokeAsync( |
| 131 | EndpointFilterInvocationContext context, |
| 132 | EndpointFilterDelegate next) |
| 133 | { |
| 134 | var argument = context.Arguments |