For simple request types where a separate handler class adds ceremony without value, you can place a static Execute method directly inside the request class (or record). The source generator discovers this pattern at compile time and emits an internal adapter that bridges it to IRequestHandler<TRequest, TResponse> — no manual wiring required.
This is especially useful when migrating from MediatR codebases that have many small command/query classes, or when you prefer a more concise single-file style.
public record Ping(int Value) : ICommand<int>
{
internal static int Execute(Ping cmd)
=> cmd.Value * 2;
}
// Send as usual — the generated adapter handles dispatch:
var result = await mediator.Send(new Ping(21)); // 42
Service parameters in the Execute signature are resolved from DI automatically:
public record Greet(string Name) : IQuery<string>
{
internal static string Execute(Greet query, IGreetingService greeter)
=> greeter.Greet(query.Name);
}
Register the service as you normally would — the self-handler adapter receives it via constructor injection:
services.AddSingleton<IGreetingService, GreetingService>();
services.AddMediator().RegisterMediatorHandlers().PrecompilePipelines();
Task<T>, ValueTask<T>, void, and Task return types are all supported:
// Async Task<T>
public record FetchData(int Id) : IQuery<DataDto>
{
internal static async Task<DataDto> Execute(FetchData query, IDataStore store, CancellationToken ct)
=> await store.GetByIdAsync(query.Id, ct);
}
// Void (ICommand<Unit>)
public record FireAndForget(string Message) : ICommand<Unit>
{
internal static void Execute(FireAndForget cmd, ILogger logger)
=> logger.LogInformation(cmd.Message);
}
The source generator:
IRequest<T> (or ICommand<T> / IQuery<T>) that contain a static Execute method__SelfHandler_* adapter class implementing IRequestHandler<TRequest, TResponse>Send() extensions, and ValidateMediatorHandlers() validationThe Execute method accepts parameters in any order. The generator identifies each parameter by type:
| Parameter type | Resolution |
|---|---|
The request type itself (TRequest) |
Passed from Handle(request, ct) |
CancellationToken |
Forwarded from Handle(request, ct) |
| Any other type | Resolved from DI via constructor injection |
| Return type | Response type | Behavior |
|---|---|---|
T |
TResponse |
Sync — wrapped in ValueTask<T> |
Task<T> |
TResponse |
Async — wrapped in ValueTask<T> |
ValueTask<T> |
TResponse |
Async — returned directly |
void |
Unit |
Sync void — returns Unit.Value |
Task |
Unit |
Async void — awaits, returns Unit.Value |
Note: Self-handling requests participate in the same dispatch path as normal handlers —
HandlerCacheThreadStatic caching, pipeline behaviors, and compile-time typed extensions all apply. The staticExecutecall is a direct call (no virtual dispatch), making it marginally faster than interface dispatch inside the adapter.