Generated Code¶
The dispatch generator produces plain .NET code — async/await, no runtime magic. This page covers the pipeline, generated methods, result types, and observability features.
Pipeline¶
The generated dispatch method composes pipeline steps as a single Eff expression using LINQ from/select. Each step is only included when the corresponding attribute is present:
| Step | Triggered by | Description |
|---|---|---|
| Authorize | [Authorize] on handler |
Checks named policy before execution |
| Validate | ValidatorType on handler attribute |
Calls Validate() and lifts errors into Eff |
| Handler | Always | Invokes the decorated handler method |
| Audit | Audited = true on handler attribute |
Records an audit entry after execution |
| Auto-commit | AutoCommit = true (default) |
Calls CommitAsync() on IAutoCommittable capabilities |
Pipeline is compile-time
Steps are not checked at runtime — the generator includes only the steps you've configured. A handler with no [Authorize], no ValidatorType, and Audited = false generates a direct call to the handler method plus auto-commit.
Generated Dispatch Methods¶
For each handler, the generator emits a Dispatch overload and a convenience method on the dispatch module class:
Generated: dispatch methods
public static partial class AppDispatch
{
private static readonly ActivitySource ActivitySource =
new("MyApp.AppDispatch", "1.0.0");
public static Eff<AppRuntime, OrderCreated> Dispatch(
CreateOrder command) =>
(from result in OrderCommands.Handle(command)
from _commit in liftEff<AppRuntime, Unit>(async rt =>
{
if (rt is IAutoCommittable c)
await c.CommitAsync(default);
return unit;
})
select result)
.WithActivity("Dispatch CreateOrder", ActivitySource,
destination: "OrderCommands");
public static Eff<AppRuntime, OrderCreated> CreateOrder(
string name, int quantity) =>
Dispatch(new CreateOrder(name, quantity));
}
The runtime type parameter (AppRuntime above) is hardcoded to the concrete runtime used by the handler — dispatch is runtime-specific by design. The C# compiler resolves overloads at the call site, so dispatching is a direct method call with full type safety.
Convenience Overloads¶
For every command and query, the generator also emits a convenience method that constructs the record and dispatches in one call. Default values from the record constructor are preserved:
// Given:
public record CreateOrder(string Name, int Quantity = 10, bool Priority = false) : ICommand;
// Generated:
public static Eff<AppRuntime, OrderCreated> CreateOrder(
string name, int quantity = 10, bool priority = false) =>
Dispatch(new CreateOrder(name, quantity, priority));
Result Types¶
QueryResult<T>¶
Offset-based pagination result with computed metadata.
public sealed record QueryResult<T>(
IReadOnlyList<T> Data,
int TotalCount,
int Page,
int PageSize);
| Property | Type | Description |
|---|---|---|
Data |
IReadOnlyList<T> |
Items for the current page |
TotalCount |
int |
Total items across all pages |
Page |
int |
Current page number (1-based) |
PageSize |
int |
Items per page |
TotalPages |
int |
Computed: ⌈TotalCount / PageSize⌉ |
HasNextPage |
bool |
Computed: Page < TotalPages |
HasPreviousPage |
bool |
Computed: Page > 1 |
[QueryHandler]
public static Eff<AppRuntime, QueryResult<OrderDto>> Handle(GetOrders query) =>
from orders in OrderStore.GetAllAsync<AppRuntime>()
let all = orders.ToList()
let page = all
.Skip((query.Page - 1) * query.PageSize)
.Take(query.PageSize)
.ToList()
select new QueryResult<OrderDto>(page, all.Count, query.Page, query.PageSize);
CursorResult<T>¶
Cursor-based pagination — stable across inserts and deletes. Use for feeds, real-time apps, or large datasets.
public sealed record CursorResult<T>(
IReadOnlyList<T> Data,
string? NextCursor,
bool HasMore,
int Limit);
| Property | Type | Description |
|---|---|---|
Data |
IReadOnlyList<T> |
Items for the current page |
NextCursor |
string? |
Opaque cursor for the next page, or null if no more |
HasMore |
bool |
Whether more items exist |
Limit |
int |
Requested page size |
// Fetch limit + 1 items to detect if more exist
var items = await store.GetAfterCursorAsync(query.Cursor, query.Limit + 1);
return CursorResult<OrderDto>.From(items, query.Limit, o => o.Id.ToString());
OpenTelemetry Tracing¶
Every generated dispatch method is wrapped with .WithActivity(), creating an OpenTelemetry span:
- Span name —
Dispatch {CommandName}(e.g.,Dispatch CreateOrder) - Destination — the handler class name (e.g.,
OrderCommands) - ActivitySource —
{Namespace}.{DispatchModuleName}with version1.0.0
This gives you end-to-end tracing through the dispatch pipeline — each command/query invocation appears as a distinct span in your traces.
Auditing¶
When Audited = true on a handler attribute, the generated pipeline records an AuditEntry after handler execution:
[CommandHandler(Audited = true)]
public static Eff<AppRuntime, OrderUpdated> Handle(UpdateOrder cmd) => ...
The generated audit step captures:
| Field | Value |
|---|---|
Id |
A new Guid (v7) |
Actor |
From IHasCorrelationContext.CorrelationContext.UserId, or "system" |
Action |
The command/query type name (e.g., "UpdateOrder") |
After |
JSON-serialized result |
Timestamp |
DateTimeOffset.UtcNow |
The entry is saved via rt.AuditStore.SaveAsync(...) on the runtime.
Direct Injection¶
The generated dispatch code is plain async/await under the hood — Eff<RT, T> is just a lazy async computation. You can run any dispatch call with .RunAsync(runtime):
var runtime = new AppRuntime(services);
var result = await AppDispatch.Dispatch(new CreateOrder("Widget", 5))
.RunAsync(runtime);
result.Match(
Succ: created => Console.WriteLine($"Created: {created.Id}"),
Fail: error => Console.WriteLine($"Failed: {error.Message}"));
No dependency injection framework is required — pass the runtime directly. See Using Generated Code for more on effects composition and direct injection.