skills/api/minimal-api/SKILL.md
Use when building minimal API endpoints with route groups, filters, or TypedResults.
npx skillsauth add faysilalshareef/dotnet-ai-kit minimal-apiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
IEndpointGroup patternTypedResults for compile-time-checked return types[AsParameters] for complex query parameter bindingpublic interface IEndpointGroup
{
void MapEndpoints(IEndpointRouteBuilder app);
}
// Auto-registration extension
public static class EndpointGroupExtensions
{
public static void MapEndpointGroups(this WebApplication app)
{
var groups = typeof(Program).Assembly
.GetTypes()
.Where(t => t.IsAssignableTo(typeof(IEndpointGroup))
&& !t.IsInterface && !t.IsAbstract)
.Select(Activator.CreateInstance)
.Cast<IEndpointGroup>();
foreach (var group in groups)
group.MapEndpoints(app);
}
}
// Program.cs
app.MapEndpointGroups();
public sealed class OrderEndpoints : IEndpointGroup
{
public void MapEndpoints(IEndpointRouteBuilder app)
{
var group = app.MapGroup("/orders")
.WithTags("Orders")
.RequireAuthorization();
group.MapGet("/", GetOrders)
.WithSummary("List orders with filtering");
group.MapGet("/{id:guid}", GetOrder)
.WithSummary("Get order by ID");
group.MapPost("/", CreateOrder)
.WithSummary("Create a new order");
group.MapPut("/{id:guid}", UpdateOrder)
.WithSummary("Update an existing order");
group.MapDelete("/{id:guid}", DeleteOrder)
.WithSummary("Delete an order");
}
private static async Task<Ok<PagedList<OrderResponse>>> GetOrders(
[AsParameters] OrderFilter filter,
ISender sender, CancellationToken ct)
{
var result = await sender.Send(
new ListOrdersQuery(filter), ct);
return TypedResults.Ok(result);
}
private static async Task<Results<Ok<OrderResponse>, NotFound>>
GetOrder(Guid id, ISender sender, CancellationToken ct)
{
var result = await sender.Send(
new GetOrderQuery(id), ct);
return result is not null
? TypedResults.Ok(result)
: TypedResults.NotFound();
}
private static async Task<Results<Created<OrderResponse>,
BadRequest<ProblemDetails>>> CreateOrder(
CreateOrderRequest request,
ISender sender, CancellationToken ct)
{
var result = await sender.Send(
new CreateOrderCommand(request.CustomerName), ct);
return result.Match<Results<Created<OrderResponse>,
BadRequest<ProblemDetails>>>(
order => TypedResults.Created(
$"/orders/{order.Id}", order),
error => TypedResults.BadRequest(
error.ToProblemDetails()));
}
}
public sealed record OrderFilter(
[FromQuery] string? CustomerName,
[FromQuery] OrderStatus? Status,
[FromQuery] int Page = 1,
[FromQuery] int PageSize = 20);
// Validation filter
public sealed class ValidationFilter<TRequest>(
IValidator<TRequest> validator) : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
var request = context.Arguments
.OfType<TRequest>().FirstOrDefault();
if (request is null)
return TypedResults.BadRequest("Request body is required");
// C-Q2 fix: forward the request's cancellation token to FluentValidation
// so client-cancelled requests stop validating mid-flight.
var result = await validator.ValidateAsync(
request, context.HttpContext.RequestAborted);
if (!result.IsValid)
{
return TypedResults.ValidationProblem(
result.ToDictionary());
}
return await next(context);
}
}
// Apply filter to endpoint
group.MapPost("/", CreateOrder)
.AddEndpointFilter<ValidationFilter<CreateOrderRequest>>();
// Program.cs
app.UseExceptionHandler(error => error.Run(async context =>
{
context.Response.ContentType = "application/problem+json";
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
await context.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = 500,
Title = "Internal Server Error",
Type = "https://tools.ietf.org/html/rfc9110#section-15.6.1"
});
}));
Program.cs (use endpoint groups)IResult without TypedResults (loses compile-time checking)CancellationToken from endpoint to handlerMapGroup, MapGet, MapPost in Program.cs or endpoint filesIEndpointGroup or IEndpointRouteBuilder extension methodsTypedResults usageWithTags, WithSummary metadata callsAddEndpointFilter callsIEndpointGroup interface and auto-discovery extensionTypedResults for explicit return type contractsWithSummary, WithTags, WithDescription[AsParameters] for complex query parameter objects| Scenario | Recommendation |
|----------|---------------|
| Simple CRUD API | Minimal API with endpoint groups |
| Complex model binding | Controllers may be easier |
| Real-time + REST | Minimal API + SignalR hubs |
| Need OpenAPI docs | Add WithSummary/WithDescription to every endpoint |
data-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.