DSoftStudio Mediator

← Back to Documentation

Requests & Handlers

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.

Defining a Request

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>;

Defining a Handler

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.

Sending a Request

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.

Handler Lifetime

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

Void Requests

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);
    }
}

Next Steps