← Docs
Koto.Api.AspNetCore
Transport-agnostic ASP.NET Core integration — Minimal APIs, MVC, or any host.
What you get
ToHttpResult()/ToHttpResultAsync()—Result<T>→IResult(Minimal API)ToActionResult()/ToActionResultAsync()—Result<T>→ActionResult<T>(MVC)KotoProblemDetails— RFC 7807 Problem Details from one or manyErrors (multiple errors become validation problem details with anerrorsdictionary per field)KotoHttpErrorOptions— extensibleError.Code→ HTTP status registry
Quick start
builder.Services.AddKotoAspNetCore(); // optional: o => o.Map("payments.gateway-failed", 502)
Minimal API:
app.MapPost("/orders", (CreateOrderCommand cmd, ICqrsDispatcher dispatcher, HttpContext ctx, CancellationToken ct)
=> dispatcher.SendAsync(cmd, ct).ToHttpResultAsync(ctx));
MVC controller:
[HttpPost]
public async Task<ActionResult<OrderDto>> Create(CreateOrderCommand cmd, CancellationToken ct)
=> (await _dispatcher.SendAsync(cmd, ct)).ToActionResult(this);
Status code mapping
Resolution order: exact code → custom rules → suffix → prefix → field-error default → fallback.
| Rule | Status |
|---|---|
*.not-found | 404 |
*.already-*, *.conflict | 409 |
*.unauthorized | 401 |
*.forbidden | 403 |
general.*, validation.*, Error.Field != null | 400 |
| everything else | 422 (fallback, configurable) |
Unmapped business errors are 422, never 500 — a failed Result is a rule violation
the client can act on; 500 is reserved for unhandled exceptions.
Customize:
builder.Services.AddKotoAspNetCore(o => o
.Map("subscription.payment-failed", StatusCodes.Status502BadGateway) // exact, highest priority
.MapSuffix(".expired", StatusCodes.Status410Gone)
.MapPrefix("quota.", StatusCodes.Status429TooManyRequests)
.Map(e => e.Code.Contains(".locked-") ? StatusCodes.Status423Locked : null));