Skip to content

1.2.0

Choose a tag to compare

@Adiakys Adiakys released this 10 May 20:43
· 39 commits to main since this release

OpenMediatR 1.2.0

Pre / Post Processors

OpenMediatR now supports IRequestPreProcessor<TRequest> and IRequestPostProcessor<TRequest, TResponse>, matching MediatR's API.

New types (in OpenMediatR.Abstractions)

  • IRequestPreProcessor<TRequest> — Task Process(TRequest, CancellationToken). Multiple pre-processors per request type are supported and run sequentially in registration order before the handler.
  • IRequestPostProcessor<TRequest, TResponse> — Task Process(TRequest, TResponse, CancellationToken). Run after the handler returns successfully; skipped when the handler or any preceding step throws.

Two registration paths

Both paths trigger automatic wiring of the matching pipeline-behavior wrappers (RequestPreProcessorBehavior<,>, RequestPostProcessorBehavior<,>).

Explicit (recommended for full control):

services.AddOpenMediatR(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<MyRequestHandler>();
    cfg.AddRequestPreProcessor<ValidateUser>();
    cfg.AddRequestPostProcessor<AuditUserAccess>();
});

AddRequestPreProcessor<T> / AddRequestPostProcessor<T> register T for every closed IRequestPreProcessor<TRequest> (or IRequestPostProcessor<TRequest, TResponse>) it implements — so a single processor class can cover multiple request types.

Opt-in assembly scan (mirrors MediatR's AutoRegisterRequestProcessors):

services.AddOpenMediatR(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<MyRequestHandler>();
    cfg.AutoRegisterRequestProcessors = true;
});

When enabled, the assembly scan picks up every processor implementation and wires the wrapper behaviors automatically. Default is false — without the flag, scanning ignores processor types.

Pipeline ordering

User pipeline behaviors registered via AddOpenBehavior wrap the processor wrappers (outer position):

  1. Outermost user behavior before logic
  2. … inner user behaviors before
  3. Pre-processors (in order)
  4. Handler
  5. Post-processors (in order)
  6. … inner user behaviors after
  7. Outermost user behavior after

Example

public sealed class ValidateUser : IRequestPreProcessor<GetUser>
{
    public Task Process(GetUser request, CancellationToken cancellationToken)
    {
        if (request.Id <= 0) throw new ArgumentException("Invalid user id.");
        return Task.CompletedTask;
    }
}

public sealed class AuditUserAccess : IRequestPostProcessor<GetUser, User>
{
    public Task Process(GetUser request, User response, CancellationToken cancellationToken)
    {
        // record the access
        return Task.CompletedTask;
    }
}

Behavior change: AddOpenMediatR no longer throws on missing assemblies

AddOpenMediatR previously threw ArgumentException when the configuration registered no assemblies. It now matches MediatR's behavior: completes silently, registers the core services, and lets you compose the rest yourself (explicit handler registrations, AddRequestPreProcessor<T>, etc.). Existing code that did supply assemblies is unaffected.

Fix: idempotent registration on repeated AddOpenMediatR calls

Calling AddOpenMediatR more than once (e.g. when composing modular DI setups) previously registered every scanned INotificationHandler<> implementation, every user pipeline behavior added via AddOpenBehavior / AddOpenStreamBehavior, and every pre/post-processor twice — causing each to execute twice on a single dispatch. Switched to TryAddEnumerable for all of them, which deduplicates by (ServiceType, ImplementationType). Different implementations of the same interface still coexist as expected.

IRequestHandler<,> and IStreamRequestHandler<,> were already idempotent via TryAddTransient and are unchanged.

Compatibility

  • Purely additive: existing Send, Publish, CreateStream, and pipeline-behavior code is unaffected.
  • Pre/post-processor scanning defaults to off; existing apps see no behavioral change unless they opt in or register processors explicitly.
  • Targets net8.0 and net10.0.