Requests & Commands
Requests are the primary dispatch mechanism in Vali-Mediator. Each request has exactly one handler.
Request Flow
loading...Defining Requests
With a Response
// A query that returns data
public record GetProductQuery(Guid ProductId) : IRequest<Result<ProductDto>>;
// A command that returns a created ID
public record CreateProductCommand(string Name, decimal Price) : IRequest<Result<Guid>>;
Void Requests
For operations with no meaningful return value, use IRequest (shorthand for IRequest<Unit>):
public record DeleteProductCommand(Guid ProductId) : IRequest;
// Or explicitly:
public record DeleteProductCommand(Guid ProductId) : IRequest<Unit>;
Implementing Handlers
Handler with Response
public class GetProductHandler : IRequestHandler<GetProductQuery, Result<ProductDto>>
{
private readonly IProductRepository _repository;
public GetProductHandler(IProductRepository repository)
{
_repository = repository;
}
public async Task<Result<ProductDto>> Handle(
GetProductQuery request,
CancellationToken cancellationToken)
{
var product = await _repository.FindByIdAsync(request.ProductId, cancellationToken);
if (product is null)
return Result<ProductDto>.Fail("Product not found.", ErrorType.NotFound);
return Result<ProductDto>.Ok(new ProductDto(product.Id, product.Name, product.Price));
}
}
Void Handler
Use the shorthand IRequestHandler<TRequest> which implements IRequestHandler<TRequest, Unit>:
public class DeleteProductHandler : IRequestHandler<DeleteProductCommand>
{
private readonly IProductRepository _repository;
public DeleteProductHandler(IProductRepository repository)
{
_repository = repository;
}
public async Task<Unit> Handle(
DeleteProductCommand request,
CancellationToken cancellationToken)
{
await _repository.DeleteAsync(request.ProductId, cancellationToken);
return Unit.Value;
}
}
Sending Requests
// Standard send
Result<ProductDto> result = await _mediator.Send(new GetProductQuery(productId));
// Void request
await _mediator.Send(new DeleteProductCommand(productId));
SendOrDefault
Returns default instead of throwing when no handler is registered:
// Returns null if no handler exists for GetProductQuery
ProductDto? product = await _mediator.SendOrDefault(new GetProductQuery(productId));
SendOrDefault is useful for optional features or plugin-based architectures where a handler may or may not be registered.
SendAll
Execute multiple requests of the same response type in parallel:
var queries = new[]
{
new GetProductQuery(id1),
new GetProductQuery(id2),
new GetProductQuery(id3),
};
// All 3 execute concurrently via Task.WhenAll
Result<ProductDto>[] results = await _mediator.SendAll(queries);
SendAll uses Task.WhenAll internally. If any request throws an unhandled exception, the whole operation fails. Use Result<T> to handle per-request errors gracefully.
Handler Registration
Handlers are auto-discovered when you call RegisterServicesFromAssemblyContaining<T>():
builder.Services.AddValiMediator(config =>
{
config.RegisterServicesFromAssemblyContaining<Program>();
// Or with explicit lifetime (default is Scoped)
config.RegisterServicesFromAssembly(
typeof(Infrastructure.Marker).Assembly,
ServiceLifetime.Transient);
});