csharp-api-controller-standards-0e12b6 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited csharp-api-controller-standards-0e12b6 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Defines the coding standards, patterns, and conventions for ASP.NET Core REST API controllers. Rules cover routing, HTTP verbs, response types, XML documentation, dependency injection, and asynchronous execution. Apply these rules uniformly to ensure a consistent, predictable, and well-documented API surface.
ControllerBase (not Controller, which includes view rendering logic).[ApiController] attribute to enable automatic model validation, API behavior conventions, and attribute routing requirements.[Route] attribute at the class level to define the base path.// ✅ Correct
[ApiController]
[Route("api/[controller]")]
public sealed class OrdersController : ControllerBase
{
}
// ❌ Wrong
public class OrdersController : Controller { }[controller] in the Route attribute to automatically use the controller name (minus the "Controller" suffix).[HttpGet("{id:guid}")]
public async Task<IActionResult> GetByIdAsync(Guid id, CancellationToken cancellationToken)Use the correct HTTP verb corresponding to the operation:
| Verb | Usage | Idempotent |
|---|---|---|
[HttpGet] | Retrieve a resource or collection | Yes |
[HttpPost] | Create a new resource or execute an action | No |
[HttpPut] | Fully update an existing resource | Yes |
[HttpPatch] | Partially update an existing resource | No |
[HttpDelete] | Remove a resource | Yes |
Explicitly state where parameters are bound from to avoid ambiguity and improve OpenAPI generation:
[FromRoute]: For IDs and path segments.[FromQuery]: For filtering, paging, and sorting parameters.[FromBody]: For complex objects in POST/PUT/PATCH requests.[HttpGet("{id:guid}/items")]
public async Task<IActionResult> GetItemsAsync(
[FromRoute] Guid id,
[FromQuery] int page,
CancellationToken cancellationToken)IActionResult or ActionResult<T>.Ok(), Created(), NotFound(), BadRequest(), NoContent()) to generate responses.Always return the appropriate HTTP status code for the outcome:
Location header pointing to the new resource.[HttpPost]
public async Task<IActionResult> CreateAsync([FromBody] CreateOrderRequest request, CancellationToken cancellationToken)
{
var id = await _service.CreateAsync(request, cancellationToken);
return CreatedAtAction(nameof(GetByIdAsync), new { id = id }, request);
}[ProducesResponseType]Explicitly declare all possible status codes and their corresponding return types using [ProducesResponseType]. This is critical for generating accurate OpenAPI/Swagger documentation.
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(OrderDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetByIdAsync([FromRoute] Guid id, CancellationToken cancellationToken)Every controller action must be fully documented using XML comments.
XML comment tags:
<summary>: A brief, single-sentence description of what the endpoint does.<remarks>: (Optional) Detailed information, usage examples, or nuances.<param>: Description of each input parameter.<returns>: Description of the HTTP response.<response>: Description of each possible HTTP status code returned./// <summary>
/// Retrieves a specific order by its unique identifier.
/// </summary>
/// <param name="id">The unique identifier of the order.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>The HTTP response.</returns>
/// <response code="200">A specific order.</response>
/// <response code="404">If the order is not found.</response>
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(OrderDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetByIdAsync([FromRoute] Guid id, CancellationToken cancellationToken)
{
// ... implementation
}Always use constructor injection for services required by the controller.
If using C# 12 or later, prefer Primary Constructors to eliminate boilerplate:
// ✅ Correct (C# 12+)
[ApiController]
[Route("api/[controller]")]
public sealed class OrdersController(
IOrderService orderService,
ILogger<OrdersController> logger) : ControllerBase
{
// Dependencies are available directly as parameters
}If using C# 11 or earlier, store dependencies in private readonly fields and use a traditional constructor:
// ✅ Correct (C# 11 and earlier)
[ApiController]
[Route("api/[controller]")]
public sealed class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
private readonly ILogger<OrdersController> _logger;
public OrdersController(IOrderService orderService, ILogger<OrdersController> logger)
{
_orderService = orderService;
_logger = logger;
}
}async Task<IActionResult> or async Task<ActionResult<T>>.Async suffix to the action method name. ASP.NET Core routing automatically removes the Async suffix when mapping route names (e.g., CreatedAtAction).CancellationToken as its final parameter and pass it down to all async service calls.// ✅ Correct
[HttpDelete("{id:guid}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteAsync([FromRoute] Guid id, CancellationToken cancellationToken)
{
var result = await _orderService.DeleteAsync(id, cancellationToken);
return result ? NoContent() : NotFound();
}Before submitting an API controller, verify:
ControllerBase and has [ApiController] and [Route] attributesapi/users)[HttpGet], [HttpPost], etc.)[FromRoute], [FromBody], etc.)async Task<IActionResult> and method name ends with AsyncCancellationToken is the last parameter and is passed down<summary>, <param>, and <response> tags are complete[ProducesResponseType] covers all possible HTTP status codes returned~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.