一键导入
api-design
Minimal API endpoint patterns with IEndpointDefinition, filters, error handling. Use when creating or modifying API endpoints.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Minimal API endpoint patterns with IEndpointDefinition, filters, error handling. Use when creating or modifying API endpoints.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Perform a thorough architecture review of a .NET project examining structure, maintainability, clarity, robustness, and goal achievement. Use when the user asks to review, audit, or examine a codebase.
CQRS implementation — commands use EF Core DbContext, queries use Dapper IDbConnection. Use when implementing or modifying command/query handlers.
Perform a thorough architecture review of a .NET project examining structure, maintainability, clarity, robustness, and goal achievement. Use when the user asks to review, audit, or examine a codebase.
EF Core configuration, value object embedding, DbUp migrations, Aspire setup. Use when modifying database schema, migrations, or data access code.
Domain entity and value object patterns — private setters, factory methods, Reconstitute. Use when creating or modifying domain models.
Debugging workflow, Docker dual-runtime requirements, dev tunnels, local CI. Use when debugging issues or working with deployment configuration.
| name | api-design |
| description | Minimal API endpoint patterns with IEndpointDefinition, filters, error handling. Use when creating or modifying API endpoints. |
| user-invocable | false |
This project uses .NET 10 Minimal APIs with an endpoint definition pattern.
public interface IEndpointDefinition
{
void DefineEndpoints(WebApplication app);
}
public class CustomerEndpoints : IEndpointDefinition
{
public void DefineEndpoints(WebApplication app)
{
// Every /api/v1 group MUST call RequireGatewayIdentity(); every route MUST declare
// RequireScope("domain:read|write"); every non-GET route MUST also call SecuredBy2Fa().
// ApiConventionTests enforces all three from the mapped endpoint metadata.
var customers = app.MapGroup("/api/v1/customers")
.WithTags("Customers")
.RequireGatewayIdentity();
customers.MapGet("/{id:int}", GetCustomer)
.WithName("GetCustomer")
.WithSummary("Get customer by ID")
.WithDescription("Retrieves a specific customer by their unique identifier")
.RequireScope("customers:read")
.Produces<CustomerReadModel>(200, "application/json")
.ProducesProblem(404)
.ProducesProblem(500);
customers.MapPost("/", CreateCustomer)
.WithName("CreateCustomer")
.WithSummary("Create a new customer")
.WithDescription("Creates a new customer with the provided information")
.RequireScope("customers:write")
.SecuredBy2Fa()
.Accepts<CreateCustomerCommand>("application/json")
.Produces<CustomerDto>(201, "application/json")
.ProducesProblem(400)
.ProducesProblem(500);
}
// Handlers MUST bind a CancellationToken and forward it to mediator.SendAsync —
// ApiConventionTests.ApiRouteEndpoints_MustBindACancellationToken enforces this.
private static async Task<IResult> CreateCustomer(
CreateCustomerCommand command, IMediator mediator, CancellationToken cancellationToken)
{
var result = await mediator.SendAsync(command, cancellationToken);
return Results.Created($"/api/v1/customers/{result.Id}", result);
}
private static async Task<IResult> GetCustomer(
int id, IMediator mediator, CancellationToken cancellationToken)
{
// GetCustomerQuery has a read-only Id set via constructor — use the ctor, not an object initializer.
var query = new GetCustomerQuery(id);
var result = await mediator.SendAsync(query, cancellationToken);
if (result == null)
return Results.NotFound();
return Results.Ok(result);
}
}
public static class EndpointExtensions
{
public static WebApplication MapApiEndpoints(this WebApplication app)
{
var endpointDefinitions = typeof(IApiMarker).Assembly
.GetTypes()
.Where(t => t.IsAssignableTo(typeof(IEndpointDefinition))
&& !t.IsAbstract && !t.IsInterface)
.Select(Activator.CreateInstance)
.Cast<IEndpointDefinition>();
foreach (var endpointDefinition in endpointDefinitions)
endpointDefinition.DefineEndpoints(app);
return app;
}
}
Use Middleware for cross-cutting concerns on all/most requests:
app.UseSerilogRequestLogging())app.UseExceptionHandler())Use Endpoint Filters for logic specific to certain endpoints/groups:
Why it matters:
// CORRECT - Use middleware for global concerns
app.UseSerilogRequestLogging();
// CORRECT - Use filter for endpoint-specific validation
public class ValidateOrderStatusFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var status = context.GetArgument<string>(0);
if (!Enum.TryParse<OrderStatus>(status, out _))
return Results.BadRequest("Invalid order status");
return await next(context);
}
}
orders.MapGet("/status/{status}", GetOrdersByStatus)
.AddEndpointFilter<ValidateOrderStatusFilter>();
RFC 7807 Problem Details with .NET 10 StatusCodeSelector:
builder.Services.AddProblemDetails(options =>
{
options.CustomizeProblemDetails = (context) =>
{
context.ProblemDetails.Extensions["traceId"] = context.HttpContext.TraceIdentifier;
};
});
// Automatic status code mapping:
// ArgumentException → 400 Bad Request
// EntityNotFoundException → 404 Not Found
// DomainRuleException → 409 Conflict
// Other exceptions (incl. bare InvalidOperationException/KeyNotFoundException — treated as server bugs) → 500 Internal Server Error