Eventuous provides two complementary approaches for wiring command services to HTTP endpoints: a Minimal API style using extension methods onDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Eventuous/eventuous/llms.txt
Use this file to discover all available pages before exploring further.
IEndpointRouteBuilder, and a controller style using the CommandHttpApiBase<TState> base class. Both patterns read the request body as the command, invoke the registered ICommandService<TState>, and translate the Result<TState> into the correct HTTP status code and problem-details payload. Install the NuGet package to get started:
Minimal API approach
MapCommand<TCommand, TState>()
The simplest registration reads the route from the [HttpCommand] attribute that you place on your command class. Eventuous resolves ICommandService<TState> from the DI container, handles the command, and returns 200 OK with Result<TState>.Ok on success.
Route, Eventuous derives the path from the command class name with the first letter lowercased, so BookRoom becomes /bookRoom.
Explicit route and enrichment
Pass the route as the first argument to override the attribute or provide one when the command class is not annotated. The optionalenrichCommand delegate lets you copy values from HttpContext (e.g., authenticated user identity) onto the command before it is dispatched:
EnrichCommandFromHttpContext<TCommand>:
CommandServiceRouteBuilder<TState> — fluent mapping
When a single state type owns several commands you can use the fluent builder returned by MapCommands<TState>() to register them all in one chain:
.MapCommand<TCommand>() call reads the route from [HttpCommand] on the command class. You can override the route per-call, and each call also accepts an enrichCommand delegate and an optional Action<RouteHandlerBuilder> for further configuration (e.g., adding metadata for Swagger):
Contract-to-command mapping
When your HTTP contract differs from your domain command (e.g., the route carries part of the data), supply aConvertAndEnrichCommand<TContract, TCommand> delegate:
[HttpCommand] attribute reference
| Property | Type | Description |
|---|---|---|
Route | string | HTTP POST route. Defaults to the camel-cased class name when omitted. |
StateType | Type | Aggregate state type. Required when used without MapCommands<TState>(). |
PolicyName | string | Comma-separated authorization policy names applied with RequireAuthorization. |
HttpCommandAttribute<TState> sets StateType at compile time:
Controller approach
CommandHttpApiBase<TState>
Derive from this base class to expose commands through MVC controllers. Inject ICommandService<TState> via the constructor (primary-constructor syntax is idiomatic) and call the protected Handle<TCommand> method from each action:
Handle<TCommand> forwards the command to the service and calls result.AsActionResult() to produce the correct HTTP response.
Custom result type — CommandHttpApiBase<TState, TResult>
When you need to shape the response body differently, inherit the two-type-parameter variant and override AsActionResult:
Contract-to-command mapping in controllers
If the HTTP contract must be converted to a different domain command, pass aCommandMap<HttpContext> to the base class and call the two-type-parameter overload of Handle:
CommandMap<HttpContext> and registered in DI before being injected into the controller.
Result mapping
Both approaches share the same mapping logic fromResult<TState> to HTTP responses:
| Exception type | HTTP status |
|---|---|
| (no exception — success) | 200 OK |
DomainException | 400 Bad Request (ValidationProblemDetails) |
AggregateNotFoundException | 404 Not Found |
OptimisticConcurrencyException | 409 Conflict |
| (any other exception) | 500 Internal Server Error |
application/problem+json content type with ProblemDetails or ValidationProblemDetails.