A minimal, dependency-light in-process mediator for .NET — MediatR-style request/response with pipeline behaviors, hand-rolled and MIT-licensed.
Targets net8.0. Single runtime dependency: Microsoft.Extensions.DependencyInjection.Abstractions (+ Microsoft.Extensions.Options for the built-in concurrency-limit behavior).
dotnet add package Llmedpublic sealed record Ping(string Message) : IRequest<string>;
public sealed class PingHandler : IRequestHandler<Ping, string>
{
public Task<string> Handle(Ping request, CancellationToken ct) =>
Task.FromResult($"pong:{request.Message}");
}var services = new ServiceCollection();
services.AddMediator(typeof(Program).Assembly);
var sp = services.BuildServiceProvider();
var mediator = sp.GetRequiredService<IMediator>();
var result = await mediator.Send(new Ping("hello")); // "pong:hello"public sealed class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
Console.WriteLine($"-> {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"<- {typeof(TRequest).Name}");
return response;
}
}
services.AddMediator(typeof(Program).Assembly)
.AddBehavior(typeof(LoggingBehavior<,>));Behaviors execute in registration order (first registered = outermost in the chain).
Useful for serializing calls to a single-forward-pass model resource.
services.AddConcurrencyLimit(maxConcurrency: 1);
services.AddMediator(typeof(Program).Assembly)
.AddBehavior(typeof(ConcurrencyLimitBehavior<,>));For Clean Architecture setups where (for example) AI-tool orchestration and domain logic should not share a behavior stack:
services.AddKeyedMediator("ai", typeof(AiHandlers).Assembly)
.AddBehavior(typeof(AiTracingBehavior<,>));
services.AddKeyedMediator("domain", typeof(DomainHandlers).Assembly)
.AddBehavior(typeof(DomainValidationBehavior<,>));
// Resolve with [FromKeyedServices("ai")] IMediator mediatorThe two mediators do not see each other's handlers or behaviors.
Each Send emits an ActivitySource("Llmed.Mediator") activity named Mediator.Send with a request.type tag. Attach an ActivityListener (or use OpenTelemetry's AddSource("Llmed.Mediator")) to capture.
MIT.