원클릭으로
minimal-api
Use when building minimal API endpoints with route groups, filters, or TypedResults.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when building minimal API endpoints with route groups, filters, or TypedResults.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when writing async code, propagating CancellationTokens, or fixing async/await pitfalls.
Use when applying or enforcing C# coding style — namespaces, sealed classes, var usage, XML docs.
Use when applying modern C# idioms — records, pattern matching, primary constructors, collection expressions.
Use when registering services, choosing lifetimes, or implementing DI patterns like decorator or keyed services.
Use when selecting or implementing design patterns in C# — factory, builder, strategy, decorator, or mediator.
| name | minimal-api |
| description | Use when building minimal API endpoints with route groups, filters, or TypedResults. |
| metadata | {"category":"api","agent":"api-designer"} |
| when_to_use | When creating minimal API endpoints, route groups, or endpoint filters |
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 |