Lithons.Mediator
0.0.7
dotnet add package Lithons.Mediator --version 0.0.7
NuGet\Install-Package Lithons.Mediator -Version 0.0.7
<PackageReference Include="Lithons.Mediator" Version="0.0.7" />
<PackageVersion Include="Lithons.Mediator" Version="0.0.7" />
<PackageReference Include="Lithons.Mediator" />
paket add Lithons.Mediator --version 0.0.7
#r "nuget: Lithons.Mediator, 0.0.7"
#:package Lithons.Mediator@0.0.7
#addin nuget:?package=Lithons.Mediator&version=0.0.7
#tool nuget:?package=Lithons.Mediator&version=0.0.7
Lithons.Mediator
A lightweight mediator for .NET with first-class support for requests, commands, and notifications — each with its own configurable middleware pipeline.
Installation
dotnet add package Lithons.Mediator
Requires .NET 10 or later.
Concepts
| Type | Interface | Handlers | Returns |
|---|---|---|---|
| Request | IRequest<T> |
exactly one | T |
| Command | ICommand / ICommand<T> |
exactly one | nothing / T |
| Notification | INotification |
zero or more | — |
Inject IMediator (or the narrower IRequestSender, ICommandSender, INotificationSender) to send messages.
Registration
Scan an assembly to auto-register all handlers:
builder.Services.AddMediator(cfg =>
{
cfg.AddHandlersFromAssembly<Program>();
});
You can pass an Assembly directly or supply a filter:
Inject IMediator (or the narrower IRequestSender, ICommandSender, INotificationSender) wherever you need to send messages.
Handler registration
Automatic discovery (recommended)
Scan an entire assembly for handlers. Open generic type definitions and abstract classes are skipped automatically.
cfg.AddHandlersFromAssembly(typeof(Program).Assembly);
cfg.AddHandlersFromAssembly<Program>(type => type.Namespace?.StartsWith("MyApp.Handlers") == true);
Or register handlers individually:
cfg.AddRequestHandler<GetUserByIdHandler>();
cfg.AddCommandHandler<CreateUserHandler>();
cfg.AddNotificationHandler<UserCreatedEmailHandler>();
Requests
Query something and get a result back. Exactly one handler must be registered.
public record GetUserById(int Id) : IRequest<UserDto>;
public class GetUserByIdHandler : IRequestHandler<GetUserById, UserDto>
{
public async Task<UserDto> Handle(GetUserById request, CancellationToken cancellationToken)
=> new UserDto(request.Id, "Alice");
}
var user = await mediator.SendAsync(new GetUserById(42), cancellationToken);
Commands
Trigger a side effect. Commands can optionally return a result.
// Without a result
public record DeleteUser(int Id) : ICommand;
public class DeleteUserHandler : ICommandHandler<DeleteUser> { /* ... */ }
public class DeleteUserHandler : ICommandHandler<DeleteUser>
{
public async Task Handle(DeleteUser command, CancellationToken cancellationToken) { /* ... */ }
}
await mediator.SendAsync(new DeleteUser(42), cancellationToken);
// With a result
public record CreateUser(string Name) : ICommand<int>;
public class CreateUserHandler : ICommandHandler<CreateUser, int>
{
public async Task<int> Handle(CreateUser command, CancellationToken cancellationToken) => newUserId;
}
int id = await mediator.SendAsync(new CreateUser("Alice"), cancellationToken);
Command strategies
| Strategy | Description |
|---|---|
CommandStrategy.Default |
Executes inline (default) |
CommandStrategy.Background |
Queues onto ICommandsChannel for background processing |
await mediator.SendAsync(new DeleteUser(42), CommandStrategy.Background, cancellationToken);
// Or configure the default
builder.Services.AddMediator(options =>
{
options.DefaultCommandStrategy = CommandStrategy.Background;
});
Background command processing
CommandStrategy.Background writes commands to an ICommandsChannel. Register the built-in processor to consume and execute them automatically:
builder.Services.AddMediator(cfg =>
{
cfg.AddBackgroundCommandProcessing();
});
This registers a DefaultCommandsChannel (unbounded, single-reader) and a CommandsBackgroundService that:
- Creates a new DI scope per command
- Resolves and invokes the handler
- Catches and logs failures per command (one poisoned command won't stop the processor)
- Uses the host's
stoppingTokenfor graceful shutdown
For bounded backpressure, pass channel options:
builder.Services.AddMediator(cfg =>
{
cfg.AddBackgroundCommandProcessing(opts =>
{
opts.Capacity = 500;
opts.FullMode = BoundedChannelFullMode.Wait;
});
});
Notifications
Broadcast an event to zero or more independent handlers.
public record UserCreated(int UserId) : INotification;
public class SendWelcomeEmailHandler : INotificationHandler<UserCreated>
{
public async Task Handle(UserCreated notification, CancellationToken cancellationToken) { /* ... */ }
}
await mediator.SendAsync(new UserCreated(42), cancellationToken);
Notification strategies
| Strategy | Description |
|---|---|
NotificationStrategy.Sequential |
Handlers run one after another (default) |
NotificationStrategy.Parallel |
Handlers run concurrently via Task.WhenAll |
await mediator.SendAsync(new UserCreated(42), NotificationStrategy.Parallel, cancellationToken);
// Or configure the default
builder.Services.AddMediator(options =>
{
options.DefaultNotificationStrategy = NotificationStrategy.Parallel;
});
Middleware pipelines
Each message type has its own pipeline. Pipelines are singletons configured once at startup.
Inline middleware:
var requestPipeline = app.Services.GetRequiredService<IRequestPipeline>();
requestPipeline.Setup(builder =>
{
builder.Use(next => async ctx =>
{
Console.WriteLine($"→ {ctx.Request.GetType().Name}");
await next(ctx);
Console.WriteLine($"← {ctx.Request.GetType().Name}");
});
builder.UseRequestHandlers(); // must be last
});
Typed middleware class:
public class LoggingMiddleware(RequestMiddlewareDelegate next) : IRequestMiddleware
{
public async ValueTask InvokeAsync(RequestContext context)
{
Console.WriteLine($"→ {context.Request.GetType().Name}");
await next(context);
Console.WriteLine($"← {context.Request.GetType().Name}");
}
}
requestPipeline.Setup(builder =>
{
builder.UseMiddleware<LoggingMiddleware>();
builder.UseRequestHandlers();
});
The same pattern applies to ICommandPipeline / UseCommandHandlers() and INotificationPipeline / UseNotificationHandlers().
| Pipeline | Interface | Context type |
|---|---|---|
IRequestPipeline |
IRequestMiddleware |
RequestContext |
ICommandPipeline |
ICommandMiddleware |
CommandContext |
INotificationPipeline |
INotificationMiddleware |
NotificationContext |
Exception handling
Register exception handlers to catch unhandled exceptions from any pipeline without writing middleware. Return true to swallow the exception or false to let it propagate.
Typed — handles exceptions for a specific message type:
public class GetUserByIdExceptionHandler : IExceptionHandler<GetUserById>
{
public ValueTask<bool> Handle(Exception exception, GetUserById message, CancellationToken cancellationToken)
=> ValueTask.FromResult(true); // handled — don’t rethrow
}
Global — catch-all for all message types:
public class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
public ValueTask<bool> Handle(Exception exception, object message, CancellationToken cancellationToken)
{
logger.LogError(exception, "Unhandled exception for {MessageType}", message.GetType().Name);
return ValueTask.FromResult(false); // not handled — rethrow
}
}
Register via MediatorConfiguration:
builder.Services.AddMediator(cfg =>
{
cfg.AddExceptionHandler<GlobalExceptionHandler>();
cfg.AddExceptionHandler<GetUserById, GetUserByIdExceptionHandler>();
});
Resolution order
- Typed —
IExceptionHandler<TMessage>is tried first. - Global —
IExceptionHandleris tried if no typed handler handled it. - Rethrow — if neither returns
true, the original exception propagates.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Lithons.Mediator.Abstractions (>= 0.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.3)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.3)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.3)
- Microsoft.Extensions.Options (>= 10.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.