ConduitR is a lightweight mediator for .NET that feels instantly familiar to MediatR users. It’s free, open source, and tuned for performance (cached pipelines, low allocations, built-in telemetry). If you’re considering an alternative as MediatR explores commercial options, ConduitR aims to be a drop-in-feeling choice with a smooth migration path.
- GitHub: https://github.com/rezabazargan/ConduitR
- NuGet (core): https://www.nuget.org/packages/ConduitR/1.0.2
Why another mediator?
Mediators help you keep controllers thin, nudge you toward CQRS style, and make cross-cutting concerns (logging, validation, retries) composable. MediatR set the bar for ergonomics in .NET. As the ecosystem evolves (and with increased discussion around commercialization/licensing), many teams want a simple, fast, open option that stays free.
ConduitR was built with that in mind:
- Familiar API:
IRequest<T>
,IRequestHandler<TReq,TRes>
,INotification
,IPipelineBehavior<,>
. - Performance-first: hot path caching for Send/Stream pipelines, minimal allocations,
ValueTask
. - Batteries as add-ons: Validation (FluentValidation), ASP.NET Core helpers, Pre/Post processors, and Polly-based resilience.
- Observability: an
ActivitySource
called"ConduitR"
so your mediator shows up in OpenTelemetry traces.
What you get (today)
-
ConduitR
(core) – mediator + telemetry spans (Mediator.Send
,Mediator.Publish
,Mediator.Stream
). -
ConduitR.Abstractions
– contracts & delegates. -
ConduitR.DependencyInjection
–AddConduit(...)
with assembly scanning. -
Add-ons:
-
ConduitR.Validation.FluentValidation
– automatic validation behavior. -
ConduitR.AspNetCore
– ProblemDetails middleware + minimal API helpers. -
ConduitR.Processing
– MediatR-style Pre/Post processors, as behaviors. -
ConduitR.Resilience.Polly
– retry, per-attempt timeout (pessimistic), circuit breaker.
-
Quick start
// Program.cs using System.Reflection; using ConduitR; using ConduitR.Abstractions; using ConduitR.DependencyInjection; var builder = WebApplication.CreateBuilder(args); builder.Services.AddConduit(cfg => { cfg.AddHandlersFromAssemblies(Assembly.GetExecutingAssembly()); cfg.PublishStrategy = PublishStrategy.Parallel; // or Sequential / StopOnFirstException }); var app = builder.Build(); app.Run(); // Request + handler public sealed record Ping(string Name) : IRequest<string>; public sealed class PingHandler : IRequestHandler<Ping, string> { public ValueTask<string> Handle(Ping req, CancellationToken ct) => ValueTask.FromResult($"Hello, {req.Name}!"); }
Notifications:
public sealed record UserRegistered(string Email) : INotification; public sealed class SendWelcomeEmail : INotificationHandler<UserRegistered> { /* … */ } public sealed class AuditLog : INotificationHandler<UserRegistered> { /* … */ } // Strategy chosen in AddConduit(...): await mediator.Publish(new UserRegistered("hi@example.com"));
Streaming:
public sealed record Ticks(int Count) : IStreamRequest<string>; public sealed class TicksHandler : IStreamRequestHandler<Ticks, string> { public async IAsyncEnumerable<string> Handle(Ticks r, [EnumeratorCancellation] CancellationToken ct) { for (var i = 1; i <= r.Count; i++) { ct.ThrowIfCancellationRequested(); await Task.Delay(100, ct); yield return $"tick-{i}"; } } } await foreach (var s in mediator.CreateStream(new Ticks(3))) Console.WriteLine(s);
Migration guide (MediatR → ConduitR)
Good news: the shapes are nearly identical.
MediatR | ConduitR |
---|---|
IMediator.Send , .Publish | same |
IRequest<TResponse> | same |
IRequestHandler<TReq,TRes> | same |
INotification / INotificationHandler<T> | same |
IPipelineBehavior<TReq,TRes> | same |
Pre/Post processors | ConduitR.Processing |
Resilience | ConduitR.Resilience.Polly |
DI switch:
// MediatR // services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); // ConduitR services.AddConduit(cfg => cfg.AddHandlersFromAssemblies(typeof(Program).Assembly));
Validation:
// MediatR: separate behavior registration // ConduitR: one line services.AddConduitValidation(typeof(Program).Assembly);
Planned tooling:
- Migration helpers: scripted transformations/aliases to cut over projects quickly.
- Roslyn analyzers: ensure “one handler per request”, flag missing registrations, encourage cancellation & streaming best practices, and offer code fixes.
Performance notes
- Cached pipelines per
(TRequest,TResponse)
forSend
andCreateStream
(no reflection or delegate recomposition on the hot path). - Lean publish path with configurable strategy.
- Built-in Activity spans with lightweight tags/events.
We’re not publishing synthetic numbers here (your app, your hardware), but in real codebases the no-magic, no-alloc approach pays off.
Cross-cutting: validation, resilience, processors
Validation (FluentValidation):
using ConduitR.Validation.FluentValidation; services.AddConduitValidation(typeof(Program).Assembly);
Resilience (Polly):
using ConduitR.Resilience.Polly; services.AddConduitResiliencePolly(o => { o.RetryCount = 3; o.Timeout = TimeSpan.FromSeconds(1); // pessimistic timeout o.CircuitBreakerEnabled = true; });
Pre/Post processors:
using ConduitR.Processing; services.AddConduitProcessing(typeof(Program).Assembly);
Pros & cons
Pros
- Familiar API → minimal migration friction.
- Free & open source.
- Performance-focused (cached pipelines,
ValueTask
, low allocations). - Built-in telemetry for OpenTelemetry.
- Optional add-ons: validation, ASP.NET Core helpers, processors, resilience.
Cons (honest)
- Newer ecosystem (fewer blog posts/samples than MediatR today).
- Analyzer pack and migration tool are on the roadmap (coming soon).
- If you depend on specific MediatR extensions, you may need to adapt or open an issue.
License & governance
ConduitR is open source and intended to remain free. Contributions are welcome—issues, PRs, and feature proposals help shape the roadmap.
- GitHub: https://github.com/rezabazargan/ConduitR
- NuGet (core): https://www.nuget.org/packages/ConduitR/1.0.2
Top comments (0)