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
dotnet add package RCommon.MediatRConfiguration
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>();
});
Publish<T>(), not Send<T>(), for MediatR eventsDeliver 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.
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:
- Registers
ISubscriber<TEvent>with the DI container. - Registers a
MediatREventHandler<TEvent, MediatRNotification<TEvent>>as anINotificationHandlerso 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
| Type | Description |
|---|---|
MediatREventHandlingBuilder | Builder used with WithEventHandling<T> to configure MediatR event handling |
IMediatREventHandlingBuilder | Interface for the MediatR event handling builder |
PublishWithMediatREventProducer | IEventProducer that publishes via IMediatorService.Publish (fan-out) |
SendWithMediatREventProducer | IEventProducer 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 |