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
                    
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="Livehop.Minimal.Mediator" Version="1.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Livehop.Minimal.Mediator" Version="1.0.4" />
                    
Directory.Packages.props
<PackageReference Include="Livehop.Minimal.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 Livehop.Minimal.Mediator --version 1.0.4
                    
#r "nuget: Livehop.Minimal.Mediator, 1.0.4"
                    
#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 Livehop.Minimal.Mediator@1.0.4
                    
#: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=Livehop.Minimal.Mediator&version=1.0.4
                    
Install as a Cake Addin
#tool nuget:?package=Livehop.Minimal.Mediator&version=1.0.4
                    
Install as a Cake Tool

Livehop.Minimal.Mediator

NuGet NuGet Downloads

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

  1. Organize by Feature - Group requests and handlers together in feature folders
  2. Use Records - Leverage C# records for immutable request/command definitions
  3. CancellationToken - Always pass and respect cancellation tokens
  4. Dependency Injection - Inject dependencies into handlers via constructor
  5. Single Responsibility - Keep handlers focused on one task
  6. BaseController - Use the BaseController pattern 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's INotification / INotificationHandler (pub/sub) are not included. Livehop.Minimal.Mediator is 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, BaseController pattern, MediatR migration guide, and SignalR hub usage

[1.0.3]

  • Added Unit struct with Unit.Value and Unit.Task
  • Added non-generic IRequest (shorthand for IRequest<Unit>) for void commands
  • Added single-arg IRequestHandler<TRequest> (shorthand for IRequestHandler<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>, IMediator
  • AddMinimalMediator() 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 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. 
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
1.0.4 674 4/9/2026
1.0.3 109 4/9/2026
1.0.2 302 2/12/2026
1.0.1 115 2/12/2026
1.0.0 116 2/12/2026