Repository navigation
1.3.0
OpenMediatR 1.3.0
Exception Handlers and Actions
OpenMediatR now supports IRequestExceptionHandler<TRequest, TResponse, TException> and IRequestExceptionAction<TRequest, TException>, completing the MediatR API parity for the request pipeline.
New types (in OpenMediatR.Abstractions)
IRequestExceptionHandler<TRequest, TResponse, TException>—Task Handle(TRequest, TException, RequestExceptionHandlerState<TResponse>, CancellationToken). May recover from the exception by callingstate.SetHandled(response).IRequestExceptionAction<TRequest, TException>—Task Execute(TRequest, TException, CancellationToken). Side-effects only; the exception is always re-thrown after all matching actions complete.RequestExceptionHandlerState<TResponse>— small DTO withHandledandResponseproperties plusSetHandled(TResponse).
Behavior
- Handler vs action ordering: when an exception bubbles up, the handler wrapper runs first. If any handler calls
SetHandled, the response is returned and the action wrapper never sees the exception. If no handler recovers, the action wrapper runs every matching action and then re-throws the original exception (stack trace preserved viaExceptionDispatchInfo). - Type-hierarchy matching: a handler/action declared for a base exception type catches derived ones.
IRequestExceptionAction<MyRequest, Exception>runs for every exception thrown duringMyRequestprocessing. The pipeline walks the actual exception's base-class chain to find every matching closed generic registration. - Multiple handlers per (request, exception) pair: run in registration order; iteration stops as soon as one handler marks the state as handled.
- Multiple actions: all matching actions run.
Registration
Exception handlers and actions are auto-scanned from assemblies unconditionally (no opt-in flag, mirroring MediatR — unlike pre/post processors which are opt-in via AutoRegisterRequestProcessors).
Explicit registration is also available:
services.AddOpenMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblyContaining<MyRequestHandler>();
cfg.AddRequestExceptionHandler<FallbackOnConnectionLost>();
cfg.AddRequestExceptionAction<LogFailures>();
});Both registration paths trigger automatic wiring of the matching pipeline-behavior wrappers. No wrappers are added when no handlers or actions are present, so apps that don't use this feature pay no overhead.
Example
public sealed class FallbackOnConnectionLost : IRequestExceptionHandler<GetUser, User, ConnectionException>
{
public Task Handle(
GetUser request,
ConnectionException exception,
RequestExceptionHandlerState<User> state,
CancellationToken cancellationToken)
{
state.SetHandled(User.Anonymous);
return Task.CompletedTask;
}
}
public sealed class LogFailures : IRequestExceptionAction<GetUser, Exception>
{
private readonly ILogger<LogFailures> _logger;
public LogFailures(ILogger<LogFailures> logger) => _logger = logger;
public Task Execute(GetUser request, Exception exception, CancellationToken cancellationToken)
{
_logger.LogError(exception, "GetUser({Id}) failed", request.Id);
return Task.CompletedTask;
}
}Compatibility
- Purely additive: existing
Send,Publish,CreateStream, pipeline behaviors, and pre/post-processors are unaffected. - Apps with no exception handlers or actions in their assemblies pay zero pipeline overhead.
- Targets
net8.0andnet10.0.
What's next
This completes the original feature parity roadmap with MediatR. Future work focuses on hardening, performance, and reacting to real-world feedback.