Requests and handlers are the core building blocks of the mediator pattern. A request is a message object that carries input data, and a handler is the component that processes it and returns a response.
A request implements IRequest<TResponse> where TResponse is the type returned by the handler:
public record Ping() : IRequest<int>;
public record GetUser(Guid Id) : IRequest<UserDto>;
public record CreateOrder(OrderInput Input) : IRequest<Guid>;
A handler implements IRequestHandler<TRequest, TResponse> and contains the business logic:
public class PingHandler : IRequestHandler<Ping, int>
{
public ValueTask<int> Handle(Ping request, CancellationToken ct)
=> new ValueTask<int>(42);
}
Handlers return ValueTask<TResponse> instead of Task<TResponse>. This avoids heap allocation when the result is available synchronously — a key design choice for zero-allocation dispatch.
Inject IMediator and call Send:
var result = await mediator.Send(new Ping());
The mediator resolves the correct handler at compile time via source-generated dispatch tables — no reflection, no MakeGenericType, no dictionary lookups in the hot path.
DSoftStudio.Mediator automatically optimizes handler lifetimes:
| Handler Type | Lifetime | Why |
|---|---|---|
| Stateless (no constructor parameters) | Singleton | Zero per-call allocation — the handler is instantiated once and reused |
| With DI dependencies | Transient | Dependencies may be scoped or transient themselves |
This is different from MediatR, where all handlers are registered as Transient by default. The auto-singleton optimization eliminates per-call allocation for stateless handlers without any manual configuration.
You can override the default lifetime by registering the handler manually before calling RegisterMediatorHandlers():
services.AddScoped<IRequestHandler<GetUser, UserDto>, GetUserHandler>();
services.RegisterMediatorHandlers(); // will not override existing registrations
For requests that don’t return a meaningful value, use Unit:
public record DeleteUser(Guid Id) : IRequest<Unit>;
public class DeleteUserHandler : IRequestHandler<DeleteUser, Unit>
{
public ValueTask<Unit> Handle(DeleteUser request, CancellationToken ct)
{
// delete logic
return new ValueTask<Unit>(Unit.Value);
}
}
ICommand<T> / IQuery<T> wrappers