Lithons.Mediator 0.0.7

dotnet add package Lithons.Mediator --version 0.0.7
                    
NuGet\Install-Package Lithons.Mediator -Version 0.0.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Lithons.Mediator" Version="0.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lithons.Mediator" Version="0.0.7" />
                    
Directory.Packages.props
<PackageReference Include="Lithons.Mediator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Lithons.Mediator --version 0.0.7
                    
#r "nuget: Lithons.Mediator, 0.0.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Lithons.Mediator@0.0.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Lithons.Mediator&version=0.0.7
                    
Install as a Cake Addin
#tool nuget:?package=Lithons.Mediator&version=0.0.7
                    
Install as a Cake Tool

Lithons.Mediator

NuGet Version NuGet Downloads Build .NET License: MIT

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

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 stoppingToken for 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

  1. TypedIExceptionHandler<TMessage> is tried first.
  2. GlobalIExceptionHandler is tried if no typed handler handled it.
  3. Rethrow — if neither returns true, the original exception propagates.
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.7 185 4/18/2026
0.0.6 103 4/4/2026
0.0.5 100 4/4/2026
0.0.4 105 3/8/2026
0.0.3 103 3/6/2026
0.0.2 120 2/19/2026
0.0.1 114 2/19/2026