Skip to content

1.4.0

Choose a tag to compare

@Adiakys Adiakys released this 10 May 22:01
· 32 commits to main since this release

OpenMediatR 1.4.0

Closes the configuration API parity gap with MediatR. Every change is additive — existing configuration code continues to work unchanged.

Closed-generic AddBehavior overloads

Pipeline behaviors bound to a specific (TRequest, TResponse) pair can now be registered without going through the open generic IPipelineBehavior<,> route.

// Implementation type only — registers under every IPipelineBehavior<,> closed interface it implements
cfg.AddBehavior<MyClosedBehavior>();

// Explicit service + implementation types — useful when one type implements multiple closed interfaces
cfg.AddBehavior<IPipelineBehavior<MyRequest, MyResponse>, MyClosedBehavior>();

// Type-based equivalents
cfg.AddBehavior(typeof(MyClosedBehavior));
cfg.AddBehavior(typeof(IPipelineBehavior<MyRequest, MyResponse>), typeof(MyClosedBehavior));

The same four overloads are available for stream pipeline behaviors via AddStreamBehavior / AddStreamBehavior<>.

Closed-generic processor two-type-arg overloads

Mirroring AddBehavior, processors now accept an explicit service type:

cfg.AddRequestPreProcessor<IRequestPreProcessor<MyRequest>, MyPreProcessor>();
cfg.AddRequestPreProcessor(typeof(IRequestPreProcessor<MyRequest>), typeof(MyPreProcessor));

cfg.AddRequestPostProcessor<IRequestPostProcessor<MyRequest, MyResponse>, MyPostProcessor>();
cfg.AddRequestPostProcessor(typeof(IRequestPostProcessor<MyRequest, MyResponse>), typeof(MyPostProcessor));

Useful when one type implements multiple closed processor interfaces and you want to wire only one of them.

Bulk open behavior registration

Register multiple open generic behaviors in one call. Each entry can carry its own lifetime override via the new OpenBehavior struct.

cfg.AddOpenBehaviors(new[]
{
    typeof(LoggingBehavior<,>),
    typeof(ValidationBehavior<,>),
}, ServiceLifetime.Singleton);

// Or with per-item lifetime
cfg.AddOpenBehaviors(new[]
{
    new OpenBehavior(typeof(LoggingBehavior<,>), ServiceLifetime.Singleton),
    new OpenBehavior(typeof(ValidationBehavior<,>)), // null → uses cfg.Lifetime
});

AddOpenStreamBehaviors follows the same shape for stream behaviors.

Open generic pre/post processors

Useful for cross-cutting processors that should run for every request type.

public sealed class LoggingPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
    where TRequest : notnull { /* ... */ }

cfg.AddOpenRequestPreProcessor(typeof(LoggingPreProcessor<>));
cfg.AddOpenRequestPostProcessor(typeof(AuditPostProcessor<,>));

The pre/post-processor wrapper behaviors are wired automatically, just like with the closed-generic AddRequestPreProcessor<T>().

RequestExceptionActionProcessorStrategy

New enum + property controlling when IRequestExceptionAction<,> implementations fire relative to IRequestExceptionHandler<,,>.

public enum RequestExceptionActionProcessorStrategy
{
    ApplyForUnhandledExceptions, // default — actions skip when a handler recovers via SetHandled
    ApplyForAllExceptions,       // actions always run; handler may then recover
}
services.AddOpenMediatR(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<MyHandler>();
    cfg.RequestExceptionActionProcessorStrategy = RequestExceptionActionProcessorStrategy.ApplyForAllExceptions;
});

Use case: you want metrics/logging to fire on every failure, even when a recovery handler is going to suppress the exception downstream.

The default ApplyForUnhandledExceptions preserves the behavior introduced in 1.3.0 — actions are skipped when a handler calls SetHandled.

Internally implemented by reversing the order of the exception-handler vs exception-action wrapper behaviors in the pipeline. No reflection, no overhead.

cfg.NotificationPublisher instance overload

Until now, the notification publisher could only be configured by Type. 1.4.0 adds an instance-based overload:

services.AddOpenMediatR(cfg =>
{
    cfg.NotificationPublisher = new MyConfiguredPublisher(threshold: 100);
});

When set, the instance takes precedence over NotificationPublisherType. Useful for publishers that need pre-built state which can't easily be wired through DI alone.

RegisterServicesFromAssemblyContaining(Type)

Type-based overload of the assembly registration shortcut, for cases where the marker type is only known at runtime.

cfg.RegisterServicesFromAssemblyContaining(markerType);

Internal cleanup

  • OpenMediatRConfiguration is now split into partial files by concern: core configuration + assembly registration in the main file, all behavior registration methods in OpenMediatRConfiguration.Behaviors.cs, all processor and exception handler/action methods in OpenMediatRConfiguration.Processors.cs.
  • Shared validation helpers (open vs closed generic checks, interface matching, assignability) extracted into private statics on the main partial — used by every Add* method to keep individual methods short and consistent.
  • Stream behaviors now have their own StreamBehaviorsToRegister collection (matches MediatR), kept separate from BehaviorsToRegister which is reserved for IPipelineBehavior<,>.

Compatibility

  • Purely additive. Existing configuration code is unaffected.
  • All new properties default to values that preserve previous behaviour (no flag flipping required when upgrading).
  • The new OpenBehavior struct and RequestExceptionActionProcessorStrategy enum live in the OpenMediatR namespace.
  • Targets net8.0 and net10.0.

What's next

The remaining MediatR-only items are deliberately omitted:

  • TypeEvaluator (custom assembly-scan filter)
  • MediatorImplementationType (custom IMediator override)
  • Public visibility of the registration collections

These are niche customization hooks that can be added on demand if real use cases surface.