Skip to content

Partie Generic Matching

SteffenBlake edited this page Sep 28, 2026 · 1 revision

Generic matching

Register a generic Provider or Partie when several handlers need the same behavior. Partie closes the step's type parameters against each route's request and result, then checks its constraints. Routes that do not match skip the step and its context dependencies.

Match any query

This Partie measures elapsed time for any query and result type:

// QueryTimingPartie.cs
public sealed class QueryTimingPartie<TQuery, TResult> :
    IQueryPartie<Unit, Unit, TQuery, TResult>
{
    public static async ValueTask<Result<TResult>> OnQueryAsync(
        Unit context,
        TQuery query,
        Next<Unit, TResult> next,
        CancellationToken cancellationToken
    )
    {
        var started = Stopwatch.GetTimestamp();
        try
        {
            return await next(Unit.Default);
        }
        finally
        {
            Console.WriteLine($"{typeof(TQuery).Name}: {Stopwatch.GetElapsedTime(started)}");
        }
    }
}

Stopwatch is in System.Diagnostics. The generated attribute omits the type parameters:

[QueryTimingPartie]
...

Each query route supplies its own TQuery and TResult. Command routes skip this step because it implements only the query contract.

Filter by a base type or interface

Put a constraint on the step's request parameter to restrict it to a family of requests:

public sealed class CourseTimingPartie<TQuery, TResult> :
    IQueryPartie<Unit, Unit, TQuery, TResult>
    where TQuery : CourseQuery
{
    ...
}

Here, CourseQuery is an application-defined base class. A route whose actual query inherits from it can match. An interface constraint works the same way.

A concrete request in the contract matches only that exact type:

public sealed class BaseCourseTimingPartie<TResult> :
    IQueryPartie<Unit, Unit, CourseQuery, TResult>
{
    ...
}

This declaration matches CourseQuery itself. A handler taking a derived query needs the generic, constrained form above. Concrete result types also match exactly; use a result type parameter and constraint when you want a broader family.

Keep type parameters resolvable

Each open type parameter on a Partie must appear in its request or result contract. A Provider may also infer a type parameter through its provided value. A type parameter used only in a context cannot be determined from the route.

Put constraints on the class. Keep the hook static and non-generic, with the same ValueTask<Result<TResult>> shape as a concrete step. A failed constraint means the step does not participate in that route, so its context does not create a dependency requirement there.

See Providers and Parties for their hook contracts.

Clone this wiki locally