ワンクリックで
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