Notifications
Notifications implement a pub-sub pattern where multiple handlers can respond to a single notification.
Defining Notifications
public record UserCreatedNotification(int UserId, string Email) : INotification;
Creating Handlers
public class SendWelcomeEmailHandler : INotificationHandler<UserCreatedNotification>
{
public async ValueTask HandleAsync(UserCreatedNotification notification, CancellationToken ct = default)
{
await _emailService.SendWelcomeAsync(notification.Email);
}
}
public class CreateAuditLogHandler : INotificationHandler<UserCreatedNotification>
{
public async ValueTask HandleAsync(UserCreatedNotification notification, CancellationToken ct = default)
{
await _auditService.LogAsync($"User {notification.UserId} created");
}
}
Publishing Notifications
await mediator.PublishAsync(new UserCreatedNotification(user.Id, user.Email));
Execution Strategies
MediatorLite provides three execution strategies, each with specific error handling behavior.
Sequential (Default)
Handlers execute one after another in order. Best for handlers with dependencies or when order matters.
services.AddMediatorLite(options =>
{
options.NotificationExecutionStrategy = NotificationExecutionStrategy.Sequential;
});
Error Strategy Behavior:
| Error Strategy | Behavior |
|---|---|
StopOnFirstError | Stops execution immediately when a handler throws. Remaining handlers are not executed. |
ContinueAndAggregate | Continues executing all handlers. All exceptions are collected and thrown as AggregateException. |
Parallel
All handlers execute concurrently using Task.WhenAll. Best for independent handlers.
services.AddMediatorLite(options =>
{
options.NotificationExecutionStrategy = NotificationExecutionStrategy.Parallel;
});
Error Strategy Behavior:
⚠️ Important:
NotificationErrorStrategyis ignored for parallel execution.
Since all handlers start immediately and run concurrently, it’s impossible to “stop on first error” - handlers cannot be cancelled mid-execution. Parallel mode always aggregates exceptions:
- All handlers run to completion
- If any fail, all exceptions are collected into
AggregateException
StopOnFirst
Executes handlers in order until one completes successfully (“first handler wins”). Useful for fallback patterns.
services.AddMediatorLite(options =>
{
options.NotificationExecutionStrategy = NotificationExecutionStrategy.StopOnFirst;
});
Error Strategy Behavior:
| Error Strategy | Behavior |
|---|---|
StopOnFirstError | If a handler throws, stops immediately and propagates the exception. No fallback. |
ContinueAndAggregate | If a handler throws, tries the next handler. Stops on first success. If all handlers fail, throws AggregateException. |
Fallback Pattern Example:
[NotificationHandlerOrder(1)]
public class PrimaryCacheHandler : INotificationHandler<GetDataNotification> { }
[NotificationHandlerOrder(2)]
public class FallbackDatabaseHandler : INotificationHandler<GetDataNotification> { }
// Configure to try next handler on failure
[NotificationOptions(
ExecutionStrategy = NotificationExecutionStrategy.StopOnFirst,
ErrorStrategy = NotificationErrorStrategy.ContinueAndAggregate)]
public record GetDataNotification(string Key) : INotification;
Strategy Comparison
| Strategy | Order Matters | Stops Early | Error Strategy |
|---|---|---|---|
| Sequential | ✅ Yes | ❌ No | ✅ Applies |
| Parallel | ❌ No | ❌ No | ❌ Always aggregates |
| StopOnFirst | ✅ Yes | ✅ On success | ✅ Applies |
Per-Notification Configuration
Override global settings using [NotificationOptions]:
[NotificationOptions(
ExecutionStrategy = NotificationExecutionStrategy.Parallel,
ErrorStrategy = NotificationErrorStrategy.ContinueAndAggregate,
OverrideGlobal = true)]
public record HighPriorityNotification(string Message) : INotification;
Global Configuration
services.AddMediatorLite(options =>
{
options.NotificationExecutionStrategy = NotificationExecutionStrategy.Sequential;
options.NotificationErrorStrategy = NotificationErrorStrategy.ContinueAndAggregate;
});
Handler Ordering
Control execution order with [NotificationHandlerOrder]:
[NotificationHandlerOrder(1)] // Executes first
public class PrimaryHandler : INotificationHandler<UserCreatedNotification> { }
[NotificationHandlerOrder(2)] // Executes second
public class SecondaryHandler : INotificationHandler<UserCreatedNotification> { }
Handlers without the attribute default to order 0.
Error Handling
try
{
await mediator.PublishAsync(notification);
}
catch (AggregateException ex)
{
// Multiple handlers threw exceptions
foreach (var inner in ex.InnerExceptions)
{
_logger.LogError(inner, "Handler failed");
}
}
Best Practices
- Use Sequential when handlers have dependencies or must run in order
- Use Parallel for independent handlers (emails, logging, analytics)
- Use StopOnFirst for fallback/circuit-breaker patterns
- Use
ContinueAndAggregatein production to ensure resilience - Handle exceptions in handlers to prevent cascade failures