Skip to main content

Notifications & Events

Notifications implement the fan-out pattern — one notification published to N handlers. They are ideal for domain events and decoupled side effects.

Fan-Out Flow​

loading...

Defining Notifications​

public record OrderPlacedEvent(
Guid OrderId,
string CustomerId,
decimal TotalAmount) : INotification;

Implementing Handlers​

// Send confirmation email (highest priority — runs first)
public class SendConfirmationEmailHandler : INotificationHandler<OrderPlacedEvent>
{
public int Priority => 10;

public async Task Handle(OrderPlacedEvent notification, CancellationToken ct)
{
await _emailService.SendOrderConfirmationAsync(
notification.CustomerId,
notification.OrderId);
}
}

// Update inventory
public class UpdateInventoryHandler : INotificationHandler<OrderPlacedEvent>
{
public int Priority => 5;

public async Task Handle(OrderPlacedEvent notification, CancellationToken ct)
{
await _inventoryService.ReserveItemsForOrderAsync(notification.OrderId);
}
}

// Track analytics (lowest priority — runs last)
public class TrackOrderAnalyticsHandler : INotificationHandler<OrderPlacedEvent>
{
// Priority defaults to 0 — no override needed
public async Task Handle(OrderPlacedEvent notification, CancellationToken ct)
{
await _analytics.TrackPurchaseAsync(notification.TotalAmount);
}
}

Priority​

The Priority property controls handler execution order. Higher value = runs first.

public interface INotificationHandler<TNotification>
{
int Priority => 0; // default implementation
Task Handle(TNotification notification, CancellationToken ct);
}
PriorityOrder
100First
10Second
0 (default)Later
-10Last

Publishing​

// Sequential (default) — handlers run one after another in priority order
await _mediator.Publish(new OrderPlacedEvent(orderId, customerId, total));

// Parallel — all handlers run concurrently (Task.WhenAll)
await _mediator.Publish(new OrderPlacedEvent(orderId, customerId, total),
PublishStrategy.Parallel);

// ResilientParallel — all handlers run even if some fail
// Failures are captured in IDeadLetterQueue if registered
await _mediator.Publish(new OrderPlacedEvent(orderId, customerId, total),
PublishStrategy.ResilientParallel);

Publish Strategy Comparison​

StrategyBehaviorOn Failure
SequentialHandlers run in priority orderFirst failure stops execution
ParallelAll handlers start concurrentlyAny failure throws AggregateException
ResilientParallelAll handlers run regardlessFailures go to IDeadLetterQueue

Notification Filter​

Implement INotificationFilter<TNotification> on a handler to conditionally skip it:

public class HighValueOrderAnalyticsHandler
: INotificationHandler<OrderPlacedEvent>,
INotificationFilter<OrderPlacedEvent>
{
public bool ShouldHandle(OrderPlacedEvent notification, CancellationToken ct)
{
// Only run this handler for orders over $1000
return notification.TotalAmount >= 1000;
}

public async Task Handle(OrderPlacedEvent notification, CancellationToken ct)
{
await _analytics.TrackHighValuePurchaseAsync(notification.TotalAmount);
}
}
note

When ShouldHandle returns false, the handler is silently skipped — no exception is thrown.