Repository navigation
Partie Parties
A Partie runs as an ordered step around a matching handler. Use one for logging, validation, access checks, and transaction boundaries. It runs even when no context requests its output.
This Partie logs a course search and its completion:
// LogCourseSearchPartie.cs
public sealed record LogCourseSearchContext(
[Inject] ILogger<LogCourseSearchPartie> Logger
);
public sealed class LogCourseSearchPartie :
IQueryPartie<Unit, LogCourseSearchContext, CourseSearchQuery, CourseSearchResult>
{
public static async ValueTask<Result<CourseSearchResult>> OnQueryAsync(
LogCourseSearchContext context,
CourseSearchQuery query,
Next<Unit, CourseSearchResult> next,
CancellationToken cancellationToken
)
{
context.Logger.LogInformation("Searching for course {CourseId}", query.CourseId);
try
{
return await next(Unit.Default);
}
finally
{
context.Logger.LogInformation("Finished course search {CourseId}", query.CourseId);
}
}
}[LogCourseSearchPartie]
...The four generic arguments are the output value, context, request, and handler result. This step provides Unit because it only adds behavior. A step can provide a useful value instead; later contexts receive it with [Provide].
Parties enter in registration order. Code after an awaited next runs as the call returns through those steps. With logging before access checks, the flow is:
Logging before
Access check before
Handler
Access check after
Logging after
Registrations on outer groups precede inner groups, followed by route registrations in source order. Bundles expand their members at the position where the bundle is registered.
An outer Partie can observe a result returned by an inner Partie. Put logging before validation when you want to record validation failures as well as handler results.
Return a result without calling next to skip all later steps and the handler:
if (!allowed)
{
return new Forbidden();
}
return await next(Unit.Default);Here, allowed represents your access check inside an asynchronous hook. The return type remains Result<TResult> for the handler's result. Earlier Parties still resume and can log or map the failure.
Calling next(value) continues the pipeline with the step's output. You can call it repeatedly, but each call can execute downstream work again. For commands, that can repeat writes. Call it once for ordinary wrapping behavior.
A returned failure travels back through earlier Parties as a normal result. An exception or cancellation also unwinds through normal C# try and finally blocks. Partie does not catch it automatically. Use finally for cleanup that must run on either path, and pass the invocation token to asynchronous work.
Implement ICommandPartie<TProvided, TContext, TCommand, TResult> with static OnCommandAsync for command behavior. It takes context, command, Next<TProvided, TResult>, and cancellation token, and returns exactly ValueTask<Result<TResult>>.
A class can implement both query and command contracts when its role, output type, and context agree. Declare both hooks. Use Generic matching to apply such a step to several request types.
See Providers for lazy values and Unit of work for the built-in command Partie.
- Home
- Getting started
-
Package guides
- Partie
- Expo
-
Mise
- Database setup
- Table mappings
- Aliases
- Relationships
- Result rows
- SQL interpolation
- Queries
- Filtering
- Joins
- Aggregation
- Paging and ordering
- Subqueries
- Set operations
- Common table expressions
- Readers
- Streaming
- Writers
- Inserts
- Updates
- Deletes
- Returned rows
- Custom SQL and procedures
- Transactions
- Resource lifetimes
- Database dialects
- Diagnostics
- Integrations
- Recipes
- Reference
- Troubleshooting
- Development