Livehop.Minimal.Mediator
1.0.4
dotnet add package Livehop.Minimal.Mediator --version 1.0.4
NuGet\Install-Package Livehop.Minimal.Mediator -Version 1.0.4
<PackageReference Include="Livehop.Minimal.Mediator" Version="1.0.4" />
<PackageVersion Include="Livehop.Minimal.Mediator" Version="1.0.4" />
<PackageReference Include="Livehop.Minimal.Mediator" />
paket add Livehop.Minimal.Mediator --version 1.0.4
#r "nuget: Livehop.Minimal.Mediator, 1.0.4"
#:package Livehop.Minimal.Mediator@1.0.4
#addin nuget:?package=Livehop.Minimal.Mediator&version=1.0.4
#tool nuget:?package=Livehop.Minimal.Mediator&version=1.0.4
Livehop.Minimal.Mediator
A minimal, lightweight mediator implementation for ASP.NET Core applications supporting the CQRS pattern with commands and queries.
Features
- ✨ Minimal and lightweight - Simple implementation with no unnecessary complexity
- 🚀 Easy to use - Drop-in replacement for MediatR with near-identical API surface
- 🎯 CQRS support - Perfect for implementing commands and queries
- 📦 Dependency Injection - Seamless integration with ASP.NET Core DI
- ⚡ Performance - Efficient handler resolution and execution
- 🔧 Flexible - Support for any request/response pattern
- 🔄 MediatR compatible -
Unit,IRequest,IRequestHandler<TRequest>all included for smooth migration
Installation
dotnet add package Livehop.Minimal.Mediator
Or in your .csproj:
<PackageReference Include="Livehop.Minimal.Mediator" Version="1.0.4" />
Quick Start
1. Register the Mediator
In your Program.cs:
using Livehop.Minimal.Mediator;
var builder = WebApplication.CreateBuilder(args);
// Register mediator — auto-discovers all handlers in the calling assembly
builder.Services.AddMinimalMediator();
var app = builder.Build();
app.Run();
2. Create a Query or Command
using Livehop.Minimal.Mediator;
namespace MyApp.Features;
public static class GetAllGames
{
// Define your request (query or command)
public record Query : IRequest<List<string>>;
// Define your handler
public class Handler : IRequestHandler<Query, List<string>>
{
public Task<List<string>> Handle(Query request, CancellationToken cancellationToken)
{
var games = new List<string> { "Game 1", "Game 2", "Game 3" };
return Task.FromResult(games);
}
}
}
3. Create a BaseController
The recommended pattern is a BaseController that lazily resolves IMediator from request services. All API controllers then inherit from it — no constructor injection needed.
// Controllers/BaseController.cs
using Livehop.Minimal.Mediator;
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Controllers;
[ApiController]
[Route("api/[controller]")]
public class BaseController : ControllerBase
{
private IMediator? _mediator;
protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetRequiredService<IMediator>();
}
4. Use in a Controller
// [ApiController] and [Route] are inherited from BaseController
public class GamesController : BaseController
{
[HttpGet]
public async Task<IActionResult> GetAllGames(CancellationToken cancellationToken)
{
var games = await Mediator.Send(new GetAllGames.Query(), cancellationToken);
return Ok(games);
}
}
Core Interfaces
IRequest<TResponse>
Marker interface for requests that return a response.
public interface IRequest<TResponse> { }
IRequest (non-generic)
Shorthand for IRequest<Unit> — use for void commands (commands that return nothing).
public interface IRequest : IRequest<Unit> { }
IRequestHandler<TRequest, TResponse>
Interface for handling requests that return a response.
public interface IRequestHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
IRequestHandler<TRequest> (single-arg)
Shorthand for IRequestHandler<TRequest, Unit> — use for void command handlers.
public interface IRequestHandler<TRequest> : IRequestHandler<TRequest, Unit>
where TRequest : IRequest<Unit> { }
Unit
Drop-in struct for MediatR's Unit. Use Unit.Value or Unit.Task when returning from void handlers.
public struct Unit
{
public static readonly Unit Value = new();
public static readonly Task<Unit> Task = System.Threading.Tasks.Task.FromResult(Value);
}
IMediator
Interface for sending requests to their handlers.
public interface IMediator
{
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
}
Advanced Usage
Void Commands (no return value)
Use the non-generic IRequest and single-arg IRequestHandler<TRequest> for commands that don't return a value:
using Livehop.Minimal.Mediator;
public static class DeleteGame
{
public record Command(Guid Id) : IRequest; // IRequest = IRequest<Unit>
public class Handler(IDbContext dbContext) : IRequestHandler<Command> // = IRequestHandler<Command, Unit>
{
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
{
var game = await dbContext.Games.FindAsync(request.Id, cancellationToken);
if (game != null)
{
dbContext.Games.Remove(game);
await dbContext.SaveChangesAsync(cancellationToken);
}
return Unit.Value;
}
}
}
Commands with Return Value
public static class CreateGame
{
public record Command(string Name, string Description) : IRequest<Guid>;
public class Handler(IDbContext dbContext) : IRequestHandler<Command, Guid>
{
public async Task<Guid> Handle(Command request, CancellationToken cancellationToken)
{
var game = new Game
{
Id = Guid.NewGuid(),
Name = request.Name,
Description = request.Description
};
await dbContext.Games.AddAsync(game, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return game.Id;
}
}
}
Queries with Dependencies
public static class GetGameById
{
public record Query(Guid Id) : IRequest<GameDto?>;
public class Handler(IDbContext dbContext, IMapper mapper)
: IRequestHandler<Query, GameDto?>
{
public async Task<GameDto?> Handle(Query request, CancellationToken cancellationToken)
{
var game = await dbContext.Games
.FirstOrDefaultAsync(g => g.Id == request.Id, cancellationToken);
return game != null ? mapper.Map<GameDto>(game) : null;
}
}
}
SignalR Hubs (primary-constructor injection)
SignalR hubs can't extend BaseController, so inject IMediator directly:
using Livehop.Minimal.Mediator;
using Microsoft.AspNetCore.SignalR;
public class GameHub(IMediator mediator) : Hub
{
public async Task SendMove(Guid gameId, string move)
{
await mediator.Send(new ProcessMove.Command(gameId, move));
}
}
Best Practices
- Organize by Feature - Group requests and handlers together in feature folders
- Use Records - Leverage C# records for immutable request/command definitions
- CancellationToken - Always pass and respect cancellation tokens
- Dependency Injection - Inject dependencies into handlers via constructor
- Single Responsibility - Keep handlers focused on one task
- BaseController - Use the
BaseControllerpattern to avoid boilerplate mediator injection in every controller
Migrating from MediatR
Livehop.Minimal.Mediator is designed as a drop-in replacement for MediatR v12. The migration requires only namespace changes — no business logic changes.
What stays the same
| Element | Status |
|---|---|
IRequest<TResponse> interface |
✅ Unchanged |
IRequest non-generic interface (void commands) |
✅ Unchanged |
IRequestHandler<TRequest, TResponse> interface |
✅ Unchanged |
IRequestHandler<TRequest> single-arg shorthand |
✅ Unchanged |
Unit struct (Unit.Value, Unit.Task) |
✅ Unchanged |
Handle(request, cancellationToken) signature |
✅ Unchanged |
IMediator interface name |
✅ Unchanged |
mediator.Send(request, cancellationToken) call |
✅ Unchanged |
| Request / Command / Query class bodies | ✅ Unchanged |
| Handler class bodies | ✅ Unchanged |
Migration steps
1. Replace the package reference in .csproj:
<PackageReference Include="MediatR" Version="12.4.1" />
<PackageReference Include="Livehop.Minimal.Mediator" Version="1.0.4" />
2. Replace service registration in Program.cs:
// Remove:
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
// Add:
services.AddMinimalMediator();
3. Replace all using MediatR; with using Livehop.Minimal.Mediator; across every .cs file — that's it for requests, handlers, and hubs.
4. Create Controllers/BaseController.cs (see Quick Start) and update controllers to extend it instead of ControllerBase.
⚠️
MediatR'sINotification/INotificationHandler(pub/sub) are not included.Livehop.Minimal.Mediatoris request/response only.
For a detailed, step-by-step migration guide with a complete checklist, see Mediator-Migration-Prompt.md.
Why Livehop.Minimal.Mediator?
- Simplicity: No complex pipeline behaviors or middleware — just requests and handlers
- Compatibility: Near-identical API to MediatR, making migration trivial
- Lightweight: Minimal dependencies and overhead
- Transparent: Simple, readable implementation you can understand and trust
Requirements
- .NET 8.0 or .NET 9.0
Microsoft.Extensions.DependencyInjection.Abstractions
Changelog
[1.0.4]
- Documentation update: comprehensive README covering
Unit, void commands,BaseControllerpattern, MediatR migration guide, and SignalR hub usage
[1.0.3]
- Added
Unitstruct withUnit.ValueandUnit.Task - Added non-generic
IRequest(shorthand forIRequest<Unit>) for void commands - Added single-arg
IRequestHandler<TRequest>(shorthand forIRequestHandler<TRequest, Unit>) - Full MediatR void-command compatibility — no code changes needed in existing handlers
[1.0.2]
- Initial public release
IRequest<TResponse>,IRequestHandler<TRequest, TResponse>,IMediatorAddMinimalMediator()extension method with automatic handler discovery
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. See CONTRIBUTING.md for guidelines.
Support
If you encounter any issues or have questions, please file an issue on GitHub.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net8.0
-
net9.0
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.