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)