Minicon.Mediator
3.0.0
dotnet add package Minicon.Mediator --version 3.0.0
NuGet\Install-Package Minicon.Mediator -Version 3.0.0
<PackageReference Include="Minicon.Mediator" Version="3.0.0" />
<PackageVersion Include="Minicon.Mediator" Version="3.0.0" />
<PackageReference Include="Minicon.Mediator" />
paket add Minicon.Mediator --version 3.0.0
#r "nuget: Minicon.Mediator, 3.0.0"
#:package Minicon.Mediator@3.0.0
#addin nuget:?package=Minicon.Mediator&version=3.0.0
#tool nuget:?package=Minicon.Mediator&version=3.0.0
Minicon.Mediator
A lightweight mediator for .NET — request/response, streaming, notifications and pipeline
behaviors with exactly one runtime dependency
(Microsoft.Extensions.DependencyInjection.Abstractions).
The type names follow MediatR and Mediator (martinothamar), so code from either migrates with
little more than a using swap. See Migrating.
Independent reimplementation by minicon eG. Not affiliated with or endorsed by the MediatR or Mediator projects. "MediatR" is a trademark of its respective owner; the familiar type names (
IRequest,INotification, …) are kept for an easy migration, but this package lives in its ownMinicon.Mediatornamespace.
3.0.0 is a breaking change.
IRequestHandler<,>.Handlenow returnsValueTask<TResponse>instead ofTask<TResponse>. Handlers that returnTaskimplementITaskRequestHandler<,>instead — one line per handler, bodies unchanged. Everything else (Send,Publish, behaviors, streaming) is untouched.
Install
dotnet add package Minicon.Mediator
Targets net10.0.
Usage
using Minicon.Mediator;
using Microsoft.Extensions.DependencyInjection;
// 1) Register — scans the given assembly for handlers
services.AddMediator(cfg => cfg.RegisterServicesFromAssemblyContaining<Ping>());
// 2) Request / Response
public record Ping(string Message) : IRequest<string>;
public sealed class PingHandler : IRequestHandler<Ping, string>
{
public ValueTask<string> Handle(Ping request, CancellationToken ct)
=> ValueTask.FromResult($"Pong: {request.Message}");
}
// …or, to keep a Task-returning handler:
public sealed class PingTaskHandler : ITaskRequestHandler<Ping, string>
{
public Task<string> Handle(Ping request, CancellationToken ct)
=> Task.FromResult($"Pong: {request.Message}");
}
var answer = await mediator.Send(new Ping("hi")); // "Pong: hi" — Send returns Task<T>
// 3) Notifications (fan-out to all handlers)
public record UserRegistered(string Email) : INotification;
public sealed class SendWelcome : INotificationHandler<UserRegistered>
{
public Task Handle(UserRegistered n, CancellationToken ct) => /* ... */ Task.CompletedTask;
}
await mediator.Publish(new UserRegistered("a@b.de"));
// 4) Pipeline behaviors (logging, validation, transactions, …)
public sealed class LoggingBehavior<TReq, TRes> : IPipelineBehavior<TReq, TRes>
where TReq : notnull
{
public async Task<TRes> Handle(TReq req, RequestHandlerDelegate<TRes> next, CancellationToken ct)
{
// before
var res = await next();
// after
return res;
}
}
Streaming
For handlers that produce many responses over time, use IStreamRequest<T> and
CreateStream. Elements are forwarded as the handler yields them — nothing is buffered,
so the consumer can break out early or cancel at any point.
public record Tail(string File) : IStreamRequest<string>;
public sealed class TailHandler : IStreamRequestHandler<Tail, string>
{
public async IAsyncEnumerable<string> Handle(Tail request, [EnumeratorCancellation] CancellationToken ct)
{
await foreach (var line in File.ReadLinesAsync(request.File, ct))
{
yield return line;
}
}
}
await foreach (var line in mediator.CreateStream(new Tail("app.log"), ct))
{
Console.WriteLine(line);
}
Streams have their own behavior interface, IStreamPipelineBehavior<TReq, TRes>, which
wraps the handler the same way IPipelineBehavior<,> does for Send:
public sealed class CountingBehavior<TReq, TRes> : IStreamPipelineBehavior<TReq, TRes>
where TReq : notnull
{
public async IAsyncEnumerable<TRes> Handle(
TReq req, StreamHandlerDelegate<TRes> next, [EnumeratorCancellation] CancellationToken ct)
{
var count = 0;
await foreach (var item in next(ct).WithCancellation(ct))
{
count++;
yield return item;
}
// after — count is complete here
}
}
Handlers are discovered by the same assembly scan as IRequestHandler<,>; stream
behaviors are registered manually (services.AddTransient<IStreamPipelineBehavior<…>, …>()).
Two behavior shapes
IPipelineBehavior<TRequest, TResponse> accepts either of two Handle overloads — implement
whichever one your existing code uses:
// MediatR shape — Task, next() / next(ct)
Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct);
// Mediator (martinothamar) shape — ValueTask, next(request, ct)
ValueTask<TResponse> Handle(TRequest request, CancellationToken ct, MessageHandlerDelegate<TRequest, TResponse> next);
The pipeline invokes the first one, so that is the allocation-free path; the second is reached through
a default interface implementation costing one closure and a ValueTask/Task conversion per call.
Implement exactly one — a behavior that implements neither compiles but recurses endlessly at runtime.
Note that the request handed to next(request, ct) is accepted for signature compatibility only: the
inner stages always run against the request the pipeline started with.
Migrating from Mediator (martinothamar)
using Mediator;→using Minicon.Mediator;services.AddMediator()(source-generated) →services.AddMediator(cfg => cfg.RegisterServicesFromAssemblyContaining<T>())IRequest<T>,IPipelineBehavior<,>,MessageHandlerDelegate<,>keep their names; behaviors in the(request, cancellationToken, next)/ValueTaskshape compile unchanged (see above).cfg.ServiceLifetimeworks as an alias ofcfg.Lifetime. Note the default differs:Transienthere (MediatR behavior) versusSingletoninMediator— set it explicitly if you relied on the latter.IRequestHandler<,>returnsValueTask<TResponse>as it does inMediator, so handlers compile unchanged.
Not carried over: Send returns Task<T>, not ValueTask<T> (await works either way, assigning to a
ValueTask<T> does not). IMessage, ICommand/IQuery and their handler interfaces have no
equivalent — use IRequest/IRequest<T>. Notifications always fan out sequentially; there is no
INotificationPublisher to swap in. INotificationHandler and IStreamRequestHandler keep their
MediatR shapes (Task / IAsyncEnumerable).
Migrating from MediatR
Four replacements:
using MediatR;→using Minicon.Mediator;services.AddMediatR(...)→services.AddMediator(...)MediatRServiceConfiguration→MediatorServiceConfiguration- On request handlers:
IRequestHandler<,>→ITaskRequestHandler<,>, keeping theTask<TResponse> Handle(...)body as is. (Or switch the return type toValueTask<TResponse>and stay onIRequestHandler<,>.)
All other type names (IRequest, IRequest<T>, IStreamRequest<T>, INotification,
IStreamRequestHandler<,>, INotificationHandler<>, IPipelineBehavior<,>,
IStreamPipelineBehavior<,>, ISender, IPublisher, IMediator, Unit) are unchanged —
including notification handlers, which keep returning Task.
License
MIT © minicon eG
| 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
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.