Skip to main content

Requests

Requests follow the request-response pattern, returning a typed result from a single handler.

For detailed request documentation, see the main README.

Complete Example

The following example implements a product catalogue query. The handler reads from a read-model repository and returns a rich DTO.

using GenDI;
using Microsoft.Extensions.DependencyInjection;
using NetMediate;

// ----- Messages -----
public record GetOrderDetailsQuery(string OrderId);

public record OrderDetailsDto(
string OrderId,
string Status,
decimal Total,
IReadOnlyList<OrderLineDto> Lines);

public record OrderLineDto(string ProductName, int Quantity, decimal UnitPrice);

// ----- Shared service -----
[ServiceInjection]
public interface IOrderReadRepository
{
Task<OrderDetailsDto?> GetByIdAsync(string orderId, CancellationToken ct);
}

// ----- Handler -----
[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 1)]
public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetailsQuery, OrderDetailsDto>
{
[Inject] public required IOrderReadRepository Repository { get; init; }

public async Task<OrderDetailsDto> Handle(GetOrderDetailsQuery query, CancellationToken ct)
{
var order = await Repository.GetByIdAsync(query.OrderId, ct);

if (order is null)
throw new KeyNotFoundException($"Order '{query.OrderId}' not found.");

return order;
}
}

// ----- Usage (Minimal API) -----
app.MapGet("/orders/{id}", async (string id, IMediator mediator, CancellationToken ct) =>
{
try
{
var details = await mediator.RequestGetOrderDetailsQueryAsync(
new GetOrderDetailsQuery(id), ct);
return Results.Ok(details);
}
catch (KeyNotFoundException ex)
{
return Results.NotFound(new { ex.Message });
}
});

Only one handler is invoked. If you register a second IRequestHandler<GetOrderDetailsQuery, OrderDetailsDto>, only the first registered one is called.