Minimal API endpoints, route groups, endpoint filters, TypedResults, and auto-discovery patterns. Trigger: minimal API, endpoints, MapGet, MapPost, route group, TypedResults.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
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");
var result = await validator.ValidateAsync(request);
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 |