HTTP endpoints
A controller action that does nothing but deserialize a request, call Send, and translate a Result
into a status code is boilerplate. Mediant.AspNetCore removes it.
dotnet add package Mediant.AspNetCoreAnnotate the command
[HttpEndpoint("POST", "/api/orders", Tags = ["Orders"], SuccessStatusCode = 201)][Transactional][Auditable]public record CreateOrder(string UserId, List<OrderItem> Items) : ICommand<Result<Guid>>;Map them
app.MapMediantEndpoints(typeof(Program).Assembly);The assembly is scanned for [HttpEndpoint], and each annotated request becomes a minimal-API route that
binds the body (or route/query values), dispatches through the full behavior pipeline, and maps the
Result to HTTP.
Attribute options
| Property | Purpose |
|---|---|
Method | "GET", "POST", … (constructor argument) |
Route | The route template (constructor argument) |
SuccessStatusCode | 200 by default; set 201 for creates |
Group | Route group to nest under |
Tags | OpenAPI tags |
Summary / Description | OpenAPI metadata |
Result maps to status
The ErrorType on the failure decides the response. Nothing to wire up.
Result | Response |
|---|---|
| Success | 200 OK (or 201 Created, if configured) |
Validation | 400 — RFC 7807 ValidationProblemDetails, one entry per field |
Unauthorized | 401 |
Forbidden | 403 |
NotFound | 404 |
Conflict | 409 |
Failure | 422 Unprocessable Entity |
Unavailable | 503 |
Internal / anything else | 500 |
All failures are returned as ProblemDetails, so a client gets a consistent, machine-readable body.
Queries with route parameters
[HttpEndpoint("GET", "/api/orders/{id}", Tags = ["Orders"])][Cacheable(durationSeconds: 60, CacheKeyPrefix = "orders:")]public record GetOrderById(Guid Id) : IQuery<Result<Order>>;{id} binds to the Id property. A cache hit skips the handler entirely, but still passes through
logging and auditing.
Keep your controllers
None of this is exclusive. MapMediantEndpoints maps only the annotated types — everything else stays in
your controllers, and you can call mediator.Send(...) from them as usual.
public class OrdersController(IMediator mediator) : ControllerBase{ [HttpPost] public async Task<IActionResult> Create(CreateOrder cmd) { var result = await mediator.Send(cmd); return result.Match(id => Created($"/api/orders/{id}", id), this.Problem); }}