Skip to content

1.3.0

Choose a tag to compare

@Adiakys Adiakys released this 10 May 21:11
· 37 commits to main since this release

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 calling state.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 with Handled and Response properties plus SetHandled(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 via ExceptionDispatchInfo).
  • Type-hierarchy matching: a handler/action declared for a base exception type catches derived ones. IRequestExceptionAction<MyRequest, Exception> runs for every exception thrown during MyRequest processing. 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.0 and net10.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.