원클릭으로
controller-patterns
Use when building controller-based REST APIs with action results, model binding, or MediatR integration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when building controller-based REST APIs with action results, model binding, or MediatR integration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when adding caching to .NET APIs or optimizing response times with distributed cache, output cache, or ETags.
Use when configuring API response formats, custom formatters, or Accept header handling.
Use when creating RESTful API controllers with MediatR dispatch and ProblemDetails error responses.
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when building minimal API endpoints with route groups, filters, or TypedResults.
Use when setting up OpenAPI spec generation or Scalar API documentation UI.
| name | controller-patterns |
| description | Use when building controller-based REST APIs with action results, model binding, or MediatR integration. |
| metadata | {"category":"api","agent":"api-designer","when-to-use":"When creating or modifying controller-based API endpoints with MediatR integration"} |
[ApiController] for automatic model validation and binding behaviorActionResult<T> with explicit [ProducesResponseType] attributesCancellationToken from every action method[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public sealed class OrdersController(ISender sender) : ControllerBase
{
[HttpGet]
[ProducesResponseType(typeof(PagedList<OrderResponse>),
StatusCodes.Status200OK)]
public async Task<ActionResult<PagedList<OrderResponse>>> GetOrders(
[FromQuery] OrderFilter filter, CancellationToken ct)
{
var result = await sender.Send(new ListOrdersQuery(filter), ct);
return Ok(result);
}
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(OrderResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<OrderResponse>> GetOrder(
Guid id, CancellationToken ct)
{
var result = await sender.Send(new GetOrderQuery(id), ct);
return result is not null ? Ok(result) : NotFound();
}
[HttpPost]
[ProducesResponseType(typeof(OrderResponse),
StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails),
StatusCodes.Status400BadRequest)]
public async Task<ActionResult<OrderResponse>> CreateOrder(
CreateOrderRequest request, CancellationToken ct)
{
var result = await sender.Send(
new CreateOrderCommand(request.CustomerName, request.Items), ct);
return CreatedAtAction(
nameof(GetOrder), new { id = result.Id }, result);
}
[HttpPut("{id:guid}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateOrder(
Guid id, UpdateOrderRequest request, CancellationToken ct)
{
var result = await sender.Send(
new UpdateOrderCommand(id, request.CustomerName), ct);
return result.Match<IActionResult>(
_ => NoContent(),
error => error.Type == ErrorType.NotFound
? NotFound() : BadRequest(error.ToProblemDetails()));
}
[HttpDelete("{id:guid}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteOrder(
Guid id, CancellationToken ct)
{
var result = await sender.Send(new DeleteOrderCommand(id), ct);
return result.IsSuccess ? NoContent() : NotFound();
}
}
[ApiController]
[Route("api/orders/{orderId:guid}/items")]
[Produces("application/json")]
public sealed class OrderItemsController(ISender sender) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<List<OrderItemResponse>>> GetItems(
Guid orderId, CancellationToken ct)
{
var result = await sender.Send(
new ListOrderItemsQuery(orderId), ct);
return Ok(result);
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> AddItem(
Guid orderId, AddOrderItemRequest request, CancellationToken ct)
{
var result = await sender.Send(
new AddOrderItemCommand(orderId, request.ProductId,
request.Quantity), ct);
return CreatedAtAction(nameof(GetItems), new { orderId }, result);
}
}
// Program.cs
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter());
options.JsonSerializerOptions.DefaultIgnoreCondition =
JsonIgnoreCondition.WhenWritingNull;
});
var app = builder.Build();
app.MapControllers();
builder.Services.AddProblemDetails(options =>
{
options.CustomizeProblemDetails = context =>
{
context.ProblemDetails.Instance =
context.HttpContext.Request.Path;
context.ProblemDetails.Extensions["traceId"] =
Activity.Current?.Id ??
context.HttpContext.TraceIdentifier;
};
});
// FromQuery — query string parameters
public async Task<IActionResult> Search(
[FromQuery] string? name,
[FromQuery] int page = 1) { }
// FromRoute — URL route parameters
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(
[FromRoute] Guid id) { }
// FromBody — request body (default for complex types with [ApiController])
[HttpPost]
public async Task<IActionResult> Create(
CreateOrderRequest request) { }
// FromHeader — custom headers
public async Task<IActionResult> Process(
[FromHeader(Name = "X-Correlation-Id")] string? correlationId) { }
CancellationToken parameter[ProducesResponseType] attributesISender/IMediator: ControllerBase or : Controller class inheritance[ApiController] attribute on classesControllers/ folderservices.AddControllers() in Program.cs[ProducesResponseType] attributes on actions[ApiController] to all API controllers for automatic validation[ProducesResponseType] to document response types for OpenAPICancellationToken parameter to all async action methodsCreatedAtAction for POST endpoints returning 201| Scenario | Recommendation |
|---|---|
| Complex model binding | Controllers handle this well |
| Need attribute routing | Controllers with [Route] |
| Rapid prototyping | Minimal API may be faster |
| Legacy migration | Keep controllers, modernize patterns |
| OpenAPI generation | Both work, controllers have richer attributes |