Skip to content

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.

Terminal window
dotnet add package Mediant.AspNetCore

Annotate 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

PropertyPurpose
Method"GET", "POST", … (constructor argument)
RouteThe route template (constructor argument)
SuccessStatusCode200 by default; set 201 for creates
GroupRoute group to nest under
TagsOpenAPI tags
Summary / DescriptionOpenAPI metadata

Result maps to status

The ErrorType on the failure decides the response. Nothing to wire up.

ResultResponse
Success200 OK (or 201 Created, if configured)
Validation400 — RFC 7807 ValidationProblemDetails, one entry per field
Unauthorized401
Forbidden403
NotFound404
Conflict409
Failure422 Unprocessable Entity
Unavailable503
Internal / anything else500

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);
}
}