Stats
Actions
Tags
Help us improve
Share bugs, ideas, or general feedback.
From dotnet-ai-kit
Use when creating RESTful API controllers with MediatR dispatch and ProblemDetails error responses.
npx claudepluginhub faysilalshareef/dotnet-ai-kitHow this skill is triggered — by the user, by Claude, or both
Slash command
/dotnet-ai-kit:controllersWhen to use
When creating RESTful API controllers with authorization and ProblemDetails
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
```csharp
Guides technical evaluation of code review feedback: read fully, restate for understanding, verify against codebase, respond with reasoning or pushback before implementing.
Share bugs, ideas, or general feedback.
namespace {Company}.{Domain}.Api.Controllers;
[ApiController]
[Route("api/v1/[controller]")]
[Produces("application/json")]
public abstract class BaseController : ControllerBase
{
private ISender? _mediator;
protected ISender Mediator =>
_mediator ??= HttpContext.RequestServices.GetRequiredService<ISender>();
}
namespace {Company}.{Domain}.Api.Controllers;
[Authorize]
public sealed class OrdersController : BaseController
{
/// <summary>Get paginated orders.</summary>
[HttpGet]
[ProducesResponseType(typeof(PaginatedList<OrderOutput>), 200)]
public async Task<IActionResult> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? search = null,
CancellationToken ct = default)
{
var query = new GetOrdersQuery(page, pageSize, search);
var result = await Mediator.Send(query, ct);
// C-Q4 fix: Problem(string? detail, ...) overload prefers explicit
// named arguments so the status code is unambiguous (otherwise the
// detail is interpreted as a path/URI). Status 400 is the canonical
// mapping for a failed Result<T> in this skill.
return result.IsSuccess
? Ok(result.Value)
: Problem(detail: result.Error, statusCode: StatusCodes.Status400BadRequest);
}
/// <summary>Get order by ID.</summary>
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(OrderOutput), 200)]
[ProducesResponseType(404)]
public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
{
var result = await Mediator.Send(new GetOrderByIdQuery(id), ct);
return result.IsSuccess
? Ok(result.Value)
: NotFound();
}
/// <summary>Create a new order.</summary>
[HttpPost]
[ProducesResponseType(typeof(Guid), 201)]
[ProducesResponseType(typeof(ValidationProblemDetails), 400)]
public async Task<IActionResult> Create(
[FromBody] CreateOrderCommand command, CancellationToken ct)
{
var result = await Mediator.Send(command, ct);
return result.IsSuccess
? CreatedAtAction(nameof(GetById), new { id = result.Value }, result.Value)
: BadRequest(result.Error);
}
/// <summary>Update an existing order.</summary>
[HttpPut("{id:guid}")]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public async Task<IActionResult> Update(
Guid id, [FromBody] UpdateOrderCommand command, CancellationToken ct)
{
if (id != command.Id)
return BadRequest("Route ID does not match body ID.");
var result = await Mediator.Send(command, ct);
return result.IsSuccess ? NoContent() : NotFound();
}
/// <summary>Delete an order.</summary>
[HttpDelete("{id:guid}")]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
{
var result = await Mediator.Send(new DeleteOrderCommand(id), ct);
return result.IsSuccess ? NoContent() : NotFound();
}
}
namespace {Company}.{Domain}.Api.Middleware;
public sealed class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
: IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext context, Exception exception, CancellationToken ct)
{
logger.LogError(exception, "Unhandled exception");
var problem = exception switch
{
ValidationException ve => new ProblemDetails
{
Status = 400, Title = "Validation Failed",
Detail = string.Join("; ", ve.Errors.Select(e => e.ErrorMessage))
},
NotFoundException => new ProblemDetails
{
Status = 404, Title = "Not Found"
},
_ => new ProblemDetails
{
Status = 500, Title = "Internal Server Error"
}
};
context.Response.StatusCode = problem.Status!.Value;
await context.Response.WriteAsJsonAsync(problem, ct);
return true;
}
}
// Program.cs
builder.Services.AddControllers();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();
app.MapControllers();
| Anti-Pattern | Correct Approach |
|---|---|
| Business logic in controllers | Delegate to MediatR handlers |
| Returning domain entities directly | Map to output DTOs |
| Manual model validation | Use FluentValidation + pipeline behavior |
| Catching exceptions per action | Use global exception handler |
grep -r "ControllerBase\|ApiController" --include="*.cs"
grep -r "IMediator\|ISender" --include="*.cs"