Skip to main content
Version: 3.2.0

MediatR

RCommon integrates with MediatR to route events through the MediatR notification pipeline. Each event is wrapped in a MediatRNotification<TEvent> and published to all registered INotificationHandler instances. From the application's perspective, handlers still implement ISubscriber<TEvent> — the MediatR plumbing is hidden behind the abstraction.

Installation

NuGet Package
dotnet add package RCommon.MediatR

Configuration

Use WithEventHandling<MediatREventHandlingBuilder> on the RCommon builder. Declare each event with Publish<T>() and register its subscriber with AddSubscriber<T,H>():

using RCommon;
using RCommon.MediatR;

builder.Services.AddRCommon()
.WithEventHandling<MediatREventHandlingBuilder>(eventHandling =>
{
eventHandling.Publish<OrderPlaced>(); // registers the MediatR producer + route
eventHandling.AddSubscriber<OrderPlaced, OrderPlacedHandler>(); // bridges to a MediatR notification handler
eventHandling.Publish<OrderShipped>();
eventHandling.AddSubscriber<OrderShipped, OrderShippedHandler>();
});
Use Publish<T>(), not Send<T>(), for MediatR events

Deliver in-process MediatR events with Publish<T>() (fan-out). The point-to-point Send<T>() verb on the MediatR builder has a known type-resolution limitation in 3.2.0 — see Known Issues. There is no mediator-native outbox; for durable relay add .UseOutbox("store") / UseRCommonOutbox("store") and register an RCommon outbox.

Modular composition

WithEventHandling<MediatREventHandlingBuilder> is cache-aware. When multiple modules call it, the cached MediatREventHandlingBuilder is reused and each configuration delegate runs against the same instance — subscriber and producer registrations from every module accumulate. AddProducer<T> deduplicates by concrete producer type: registering PublishWithMediatREventProducer from both OrderingModule and NotificationsModule results in a single descriptor. See Modular Composition for the full conflict matrix.

In-process MediatR dispatch needs both verbs: Publish<TEvent>() registers PublishWithMediatREventProducer and records the route, while AddSubscriber<TEvent, THandler> bridges your ISubscriber<TEvent> to a MediatR notification handler. AddSubscriber does two things internally:

  1. Registers ISubscriber<TEvent> with the DI container.
  2. Registers a MediatREventHandler<TEvent, MediatRNotification<TEvent>> as an INotificationHandler so MediatR can dispatch the notification.

Unlike the broker builders, AddSubscriber is the correct (non-obsolete) verb on the in-process MediatR builder — MediatR has no Consume.

Custom MediatR service configuration

If you need to control which assemblies MediatR scans for handlers, use the three-parameter overload:

builder.Services.AddRCommon()
.WithEventHandling<MediatREventHandlingBuilder>(
eventHandling =>
{
eventHandling.Publish<OrderPlaced>();
eventHandling.AddSubscriber<OrderPlaced, OrderPlacedHandler>();
},
mediatR =>
{
mediatR.RegisterServicesFromAssembly(typeof(Program).Assembly);
});

Defining an event

Events must implement ISerializableEvent. The ISyncEvent and IAsyncEvent markers control dispatch ordering through the IEventRouter:

using RCommon.Models.Events;

public class OrderPlaced : ISyncEvent
{
public OrderPlaced(Guid orderId, DateTime placedAt)
{
OrderId = orderId;
PlacedAt = placedAt;
}

public OrderPlaced() { }

public Guid OrderId { get; }
public DateTime PlacedAt { get; }
}

Implementing a subscriber

Implement ISubscriber<TEvent>. MediatR delivery is transparent:

using RCommon.EventHandling.Subscribers;

public class OrderPlacedHandler : ISubscriber<OrderPlaced>
{
private readonly ILogger<OrderPlacedHandler> _logger;

public OrderPlacedHandler(ILogger<OrderPlacedHandler> logger)
{
_logger = logger;
}

public async Task HandleAsync(OrderPlaced @event, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Order {OrderId} placed at {PlacedAt}", @event.OrderId, @event.PlacedAt);
await Task.CompletedTask;
}
}

Publishing events

Produce events through IEventRouter or IEventProducer. The producer wraps each event in a MediatRNotification<TEvent> and calls IMediatorService.Publish, which dispatches to all registered notification handlers:

public class OrderService
{
private readonly IEventRouter _eventRouter;

public OrderService(IEventRouter eventRouter)
{
_eventRouter = eventRouter;
}

public async Task PlaceOrderAsync(Order order, CancellationToken cancellationToken)
{
// ... persist the order ...

_eventRouter.AddTransactionalEvent(new OrderPlaced(order.Id, DateTime.UtcNow));
await _eventRouter.RouteEventsAsync(cancellationToken);
}
}

How MediatR is wired

Internally, PublishWithMediatREventProducer calls IMediatorService.Publish(@event, cancellationToken). The mediator service wraps the event in a MediatRNotification<TEvent> and dispatches it through MediatR's notification pipeline. MediatREventHandler<TEvent, TNotification> receives the notification and resolves ISubscriber<TEvent> from the DI container to call HandleAsync.

This means MediatR pipeline behaviours (logging, validation, unit-of-work) apply to event handlers just as they apply to command and query handlers, giving you a consistent cross-cutting-concerns story.

API summary

TypeDescription
MediatREventHandlingBuilderBuilder used with WithEventHandling<T> to configure MediatR event handling
IMediatREventHandlingBuilderInterface for the MediatR event handling builder
PublishWithMediatREventProducerIEventProducer that publishes via IMediatorService.Publish (fan-out)
SendWithMediatREventProducerIEventProducer that sends via IMediatorService.Send (point-to-point)
MediatREventHandler<TEvent, TNotification>Internal INotificationHandler that bridges MediatR to ISubscriber<TEvent>
MediatRNotification<TEvent>MediatR INotification wrapper used to carry events through the pipeline
RCommonRCommon