Migration from MediatR
This guide helps you migrate from MediatR to MediatorLite.
Interface Mapping
| MediatR | MediatorLite | Notes |
|---|---|---|
IRequest<TResponse> | IRequest<TResponse> | Same |
IRequest | IRequest | Same |
IRequestHandler<TRequest, TResponse> | IRequestHandler<TRequest, TResponse> | Handler returns ValueTask<T> for performance |
INotification | INotification | Same |
INotificationHandler<T> | INotificationHandler<T> | Handler returns ValueTask |
IPipelineBehavior<TRequest, TResponse> | IPipelineBehavior<TRequest, TResponse> | Behavior returns ValueTask<T> |
Unit | Unit | Same concept |
IMediator.Send<T>() returns Task<T> | IMediator.SendAsync<T>() returns Task<T> | Same return type for consumer ergonomics |
IMediator.Publish() returns Task | IMediator.PublishAsync() returns Task | Same return type for consumer ergonomics |
Key Differences
1. Public API: Task-based for Consumer Ergonomics
MediatorLite’s IMediator interface returns Task<T> and Task for maximum consumer ergonomics, enabling natural parallel execution patterns:
// MediatorLite supports natural parallel execution
var task1 = _mediator.SendAsync(new GetUserQuery(1));
var task2 = _mediator.SendAsync(new GetOrderQuery(1));
await Task.WhenAll(task1, task2); // Works naturally!
2. Handler Internals: ValueTask for Performance
Internally, handlers use ValueTask<T> for better performance on synchronous completion paths:
MediatR:
public class MyHandler : IRequestHandler<MyQuery, Result>
{
public Task<Result> Handle(MyQuery request, CancellationToken ct)
{
return Task.FromResult(new Result()); // Allocates Task
}
}
MediatorLite:
public class MyHandler : IRequestHandler<MyQuery, Result>
{
public ValueTask<Result> HandleAsync(MyQuery request, CancellationToken ct = default)
{
return ValueTask.FromResult(new Result()); // Zero allocation for sync completion
}
}
3. Method Name: Handle -> HandleAsync
| MediatR | MediatorLite |
|---|---|
Handle() | HandleAsync() |
Send() | SendAsync() |
Publish() | PublishAsync() |
4. Registration
MediatR uses runtime assembly scanning:
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblyContaining<Program>();
cfg.AddOpenBehavior(typeof(LoggingBehavior<,>));
});
MediatorLite uses compile-time source generation:
using MediatorLite.Generated;
services
.AddGeneratedHandlers() // Source-generated: zero reflection at startup
.AddMediatorLite(options =>
{
options.AddOpenBehavior(typeof(LoggingBehavior<,>));
});
Or register handlers manually with standard DI:
services.AddTransient<IRequestHandler<MyQuery, Result>, MyQueryHandler>();
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddMediatorLite();
5. Pipeline Behaviors
MediatR:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
return await next();
}
}
MediatorLite:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async ValueTask<TResponse> HandleAsync(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct = default)
{
return await next();
}
}
Migration Steps
Step 1: Update Package Reference
<!-- Remove -->
<PackageReference Include="MediatR" Version="..." />
<!-- Add -->
<PackageReference Include="MediatorLite" Version="1.0.0" />
<PackageReference Include="MediatorLite.SourceGeneration" Version="1.0.0" />
If you keep requests/notifications in a separate shared project, use this there instead:
<PackageReference Include="MediatorLite.Abstractions" Version="1.0.0" />
Notes:
- Installing
MediatorLitepullsMediatorLite.Abstractionstransitively. - Installing only
MediatorLite.SourceGenerationdoes not provide runtime mediator contracts.
Step 2: Update Using Statements
// Remove
using MediatR;
// Add
using MediatorLite;
Step 3: Update Handlers
For handlers, update the return type and method name:
Task<T> Handle(->ValueTask<T> HandleAsync(Task Handle(->ValueTask HandleAsync(Task.FromResult(x)->ValueTask.FromResult(x)Task.CompletedTask->ValueTask.CompletedTask- Add
= defaultto CancellationToken parameters
Step 4: Update Mediator Calls
// Before
await _mediator.Send(query);
await _mediator.Publish(notification);
// After
await _mediator.SendAsync(query);
await _mediator.PublishAsync(notification);
Step 5: Update Registration
Replace MediatR’s runtime assembly scanning with source-generated registration:
// Before (MediatR)
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(...));
// After (MediatorLite - source generation)
using MediatorLite.Generated;
services
.AddGeneratedHandlers() // Compile-time handler discovery
.AddMediatorLite();
// Or: manual DI registration
services.AddTransient<IRequestHandler<MyQuery, MyResult>, MyQueryHandler>();
services.AddMediatorLite();
Source Generation
MediatorLite includes a Roslyn source generator (MediatorLite.SourceGeneration) that discovers handlers, notification handlers, and pipeline behaviors at compile time.
How It Works
The source generator scans your project for types implementing:
IRequestHandler<TRequest, TResponse>INotificationHandler<TNotification>IPipelineBehavior<TRequest, TResponse>
It generates a MediatorLiteRegistration class with extension methods to register all discovered types with the DI container.
Registration Methods
using MediatorLite.Generated;
// Register everything at once
services.AddGeneratedHandlers();
// Or register specific categories
services.AddGeneratedRequestHandlers(); // Only request handlers
services.AddGeneratedNotificationHandlers(); // Only notification handlers
services.AddGeneratedBehaviors(); // Only pipeline behaviors
Zero-Reflection Dispatch
AddGeneratedHandlers() also registers a SourceGeneratedMediator that implements ISourceGeneratedMediator. This enables the mediator to dispatch requests to handlers using direct typed method calls instead of reflection-based MethodInfo.Invoke().
The source-generated mediator provides:
- Typed request dispatch - Direct handler invocation without
MakeGenericType/MethodInfo.Invoke - Typed behavior resolution - Resolve
IPipelineBehavior<TRequest, TResponse>without reflection - Handler ordering - Compile-time lookup of
[NotificationHandlerOrder]attributes - Notification options - Compile-time lookup of
[NotificationOptions]attributes
Excluding Types
Use [MediatorGeneration(Skip = true)] to exclude a type from source generation:
[MediatorGeneration(Skip = true)]
public class TestHandler : IRequestHandler<TestQuery, string>
{
// Not registered by AddGeneratedHandlers()
}
Diagnostics
Console.WriteLine($"Request handlers: {MediatorLiteRegistration.RequestHandlerCount}");
Console.WriteLine($"Notification handlers: {MediatorLiteRegistration.NotificationHandlerCount}");
Console.WriteLine($"Behaviors: {MediatorLiteRegistration.BehaviorCount}");
Regex for Bulk Migration
Handler Method Signature
Find:
public (async )?Task<(.+?)> Handle\((.+?) request, CancellationToken (\w+)\)
Replace:
public $1ValueTask<$2> HandleAsync($3 request, CancellationToken $4 = default)
Mediator Calls
Find: \.Send\( -> Replace: .SendAsync( Find: \.Publish\( -> Replace: .PublishAsync(
Notification Execution Strategies
MediatorLite provides enhanced control over notification execution that differs from MediatR’s default behavior.
Strategy Options
| Strategy | MediatR | MediatorLite |
|---|---|---|
| Sequential execution | Default (no option) | NotificationExecutionStrategy.Sequential |
| Parallel execution | Not built-in | NotificationExecutionStrategy.Parallel |
| Stop on first success | Not available | NotificationExecutionStrategy.StopOnFirst |
Error Handling Strategies
| Error Strategy | Behavior |
|---|---|
StopOnFirstError | Stop execution and throw immediately (MediatR’s behavior) |
ContinueAndAggregate | Execute all handlers, aggregate exceptions |
Strategy-Specific Behavior
MediatorLite applies error strategies based on the execution mode:
| Execution Strategy | Error Strategy Effect |
|---|---|
| Sequential | Both strategies work as expected |
| Parallel | Error strategy ignored - always aggregates* |
| StopOnFirst | Both strategies work as expected |
*Parallel mode always aggregates exceptions because concurrent tasks cannot be cancelled mid-execution. This is by design.
Configuration Example
services.AddMediatorLite(options =>
{
// MediatR-like behavior (sequential, stop on first error)
options.NotificationExecutionStrategy = NotificationExecutionStrategy.Sequential;
options.NotificationErrorStrategy = NotificationErrorStrategy.StopOnFirstError;
// Or: More resilient production setup
options.NotificationExecutionStrategy = NotificationExecutionStrategy.Parallel;
options.NotificationErrorStrategy = NotificationErrorStrategy.ContinueAndAggregate;
});
Per-Notification Override
[NotificationOptions(
ExecutionStrategy = NotificationExecutionStrategy.StopOnFirst,
ErrorStrategy = NotificationErrorStrategy.ContinueAndAggregate,
OverrideGlobal = true)]
public record FallbackNotification(string Message) : INotification;
See Notifications documentation for detailed strategy behavior.
Features Not Available in MediatorLite v1.0
| MediatR Feature | MediatorLite Status |
|---|---|
IStreamRequest<T> | Not in v1.0 |
CreateScope() | Not needed (use DI scopes) |
ServiceFactory | Not in v1.0 (use DI directly) |