"Add or modify a CoreEx API controller (or Minimal API endpoint) in an *.Api host. USE FOR: scaffolding the MVC controller pair (XxxController + XxxReadController), GET/query/schema endpoints, POST create, PUT + PATCH full-entity update, DELETE, and custom business-action endpoints. Covers both exception-based and Result<T> service styles, and Minimal API as an alternative to MVC. DO NOT USE FOR: Api host setup / Program.cs (use coreex-scaffold), application services (use coreex-app-service), API integration tests (use coreex-test-api)."
Resources
1Install
npx skillscat add avanade/coreex/coreex-api Install via the SkillsCat registry.
CoreEx: API Controller
Guides you through adding or modifying HTTP API endpoints in an *.Api host. Covers the MVC controller pair (mutations + reads), all standard CRUD verbs, custom business-action endpoints, and Minimal API as an alternative.
When to Use
- Scaffold a new
{Name}Controller+{Name}ReadControllerpair for an entity - Add a GET by id, query, or
$queryschema endpoint - Add a POST create (with Location header and idempotency)
- Add a PUT + PATCH full-entity update pair
- Add a DELETE endpoint
- Add a custom business-action endpoint (POST/PUT that isn't a standard create/replace)
- Convert or add a Minimal API endpoint alternative
When Not to Use
- Api host setup and
Program.cscomposition — usecoreex-scaffold(see `/.github/instructions/coreex-host-setup.instructions.md`) - Application service creation — use
coreex-app-service - API integration tests — use
coreex-test-api(hand off once the endpoint is implemented) - Subscriber or relay hosts — controllers do not belong there
Quick Reference
Clarifying questions before writing any code:
0. Resolve rop-enabled (exception vs Result<T> service style — selects the standard vs WithResult WebApi helper variant) from the solution-root AGENTS.md Feature Configuration before asking the rest; only prompt for what is unrecorded and re-state resolved values for confirmation.
- What entity / resource is being exposed? (names the controller and route)
- Which operations? GET / Query / Create / Update / Delete / custom business action?
- Is the application service exception-based or
Result<T>pipeline style? (determines WebApi helper variant) - Is a read service (
I{Name}ReadService) already present, or does it need to be created? - MVC controllers or Minimal API?
Key rules at a glance:
- If Q4 answer is "needs to be created": stop before scaffolding the controller pair and invoke
coreex-app-service(Path C — CQRS Read Service) to createI{Name}ReadService/{Name}ReadServicefirst. Never shortcut by adding query/collection methods to the existingI{Name}Service/{Name}Service. - Inherit from
ControllerBase— neverController(that adds View support) - CQRS split:
{Name}Controller(POST/PUT/PATCH/DELETE →I{Name}Service) +{Name}ReadController(GET/query →I{Name}ReadService). Both use the same route and same[OpenApiTag]so they appear as one OpenAPI group - All action methods return
Task<IActionResult>via theWebApihelper — neverActionResult<T>directly - Route parameter validation: use
.Required()— not.ThrowIfNull()(wrong exception type → 500 not 400) ro.Value.Adjust(v => v.Id = id.Required())— bind routeidinto the deserialized body before passing to the servicero.WithLocationUri(...)— set theLocationresponse header in POST 201 responses[IdempotencyKey]on every create-style POST — confirm with user; omit only if explicitly non-idempotent- Always expose both PUT and PATCH for full-entity updates; specialised partial-update endpoints only on request
- Every action method takes
CancellationToken cancellationToken = default— pass to the WebApi helper viacancellationToken:and to the service via the lambda'sct:(ro, ct) => _service.XxxAsync(... , ct). Never discard with(ro, _) - Exception-based service → standard helpers (
GetAsync,PostAsync,PutAsync, …) Result<T>service →WithResultvariants (GetWithResultAsync,PostWithResultAsync,PutWithResultAsync, …)- No business logic in controllers — delegate immediately to the application service
[Query(supportsOrderBy: true), Paging(supportsCount: true)]+[HttpGet("$query")]schema endpoint for query operations- After adding a GET/Query endpoint, check whether this host already has GraphQL enabled (grep
Program.csforAddCoreExGraphQLLite, or check the host'sAGENTS.mdfor a**GraphQL:**line). If enabled, offer to add a matching root viacoreex-graphqlin the same session; if not enabled, say nothing — do not offer to enable GraphQL, that is a separate explicit ask - Once the endpoint is implemented, hand off to
coreex-test-apito add/update its integration test — go straight there. Do not write a throwaway smoke test (a scratch test class, ad-hoc curl/HttpClientcall, or manualdotnet runcheck) first "to see if it works" and then discard it; the real integration test authored bycoreex-test-apiruns against the real host/DB and is the verification step, so a preliminary pass is wasted effort that gets thrown away seconds later
For full workflow and code examples see `references/workflow.md`.
Key References
- `/.github/instructions/coreex-api-controllers.instructions.md` — authoritative conventions: MVC vs Minimal API, WebApi helpers, attributes, route parameter rules, CQRS split
- `/.github/instructions/coreex-host-setup.instructions.md` — Api host /
Program.cscomposition (scaffolded bycoreex-scaffold) - Related skills: `coreex-app-service` (controllers delegate to it), `coreex-test-api` (integration tests for these endpoints), `coreex-subscriber` (sibling host entry point), `coreex-scaffold` (host setup), `coreex-graphql` (optional GraphQL-lite root alongside a query endpoint)
- Illustrative examples (CoreEx sample — not present in your project):
- `ProductController` + `ProductReadController` — exception-based, full CRUD + query + $query
- `BasketController` + `BasketReadController` — Result<T> style, custom business actions, cross-tagged nested route
- `OrderController` — exception-based, custom orchestration action returning 202 Accepted