ConduitR 1.0.5

There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package ConduitR --version 1.0.5
                    
NuGet\Install-Package ConduitR -Version 1.0.5
                    
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="ConduitR" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ConduitR" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="ConduitR" />
                    
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 ConduitR --version 1.0.5
                    
#r "nuget: ConduitR, 1.0.5"
                    
#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 ConduitR@1.0.5
                    
#: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=ConduitR&version=1.0.5
                    
Install as a Cake Addin
#tool nuget:?package=ConduitR&version=1.0.5
                    
Install as a Cake Tool

ConduitR

Lightweight, fast, and familiar mediator primitives for modern .NET applications.

Build NuGet License

ConduitR gives you a clean way to keep application logic out of controllers, endpoints, background jobs, and UI handlers. Model work as requests, notifications, streams, and pipeline behaviors; ConduitR handles dispatch, composition, and observability.

It is intentionally familiar if you have used MediatR, but tuned around a small core, low allocation paths, ValueTask, cached generic invokers, and optional packages that you only add when you need them.

Highlights

  • Familiar request/handler model: IRequest<TResponse>, IRequestHandler<TRequest,TResponse>, INotification, and pipeline behaviors.
  • Fast hot paths: cached send and stream invokers, lean publish execution, minimal reflection after first use.
  • Async-first: ValueTask for request handling and IAsyncEnumerable<T> for streaming.
  • Modular packages: dependency injection, validation, ASP.NET Core helpers, processors, and Polly resilience live in focused add-ons.
  • Production observability: built-in ActivitySource spans for send, publish, and stream operations.
  • MediatR-friendly migration: the mental model and naming are deliberately close.

Packages

Package Purpose
ConduitR Core mediator implementation and telemetry
ConduitR.Abstractions Public request, handler, notification, stream, and pipeline contracts
ConduitR.DependencyInjection AddConduit(...), assembly scanning, and Microsoft DI integration
ConduitR.Validation.FluentValidation FluentValidation pipeline behavior and registration helpers
ConduitR.AspNetCore ProblemDetails middleware and Minimal API mapping helpers
ConduitR.Processing Request pre-processors and post-processors as pipeline behaviors
ConduitR.Resilience.Polly Retry, timeout, and circuit-breaker pipeline behavior support
ConduitR.Visualizer.Cli .NET tool that generates Markdown, JSON, and Mermaid mediator flow reports
ConduitR.Visualizer.Core Reusable analysis/reporting engine behind Visualizer tooling
ConduitR.Visualizer.Analyzers Visual Studio/Roslyn diagnostics and handler navigation lightbulbs

Installation

For most applications, start with the core packages and DI integration:

dotnet add package ConduitR
dotnet add package ConduitR.Abstractions
dotnet add package ConduitR.DependencyInjection

Add optional capabilities as your app grows:

dotnet add package ConduitR.Validation.FluentValidation
dotnet add package ConduitR.AspNetCore
dotnet add package ConduitR.Processing
dotnet add package ConduitR.Resilience.Polly

Quick Start

Register ConduitR and scan the assembly that contains your handlers:

using System.Reflection;
using ConduitR;
using ConduitR.Abstractions;
using ConduitR.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddConduit(options =>
{
    options.AddHandlersFromAssemblies(Assembly.GetExecutingAssembly());
    options.PublishStrategy = PublishStrategy.Parallel;
});

var app = builder.Build();

app.MapGet("/hello/{name}", async (string name, IMediator mediator) =>
{
    var message = await mediator.Send(new SayHello(name));
    return Results.Ok(new { message });
});

app.Run();

Define a request and handler:

using ConduitR.Abstractions;

public sealed record SayHello(string Name) : IRequest<string>;

public sealed class SayHelloHandler : IRequestHandler<SayHello, string>
{
    public ValueTask<string> Handle(SayHello request, CancellationToken cancellationToken)
    {
        return ValueTask.FromResult($"Hello, {request.Name}.");
    }
}

That is the basic ConduitR loop: endpoints create requests, handlers own the application behavior, and pipeline behaviors can wrap the work without cluttering endpoint code.

A More Realistic Flow

Requests are a good fit for commands and queries. The endpoint stays thin, while validation, logging, resilience, telemetry, and other cross-cutting behavior can be composed around the handler.

public sealed record CreateOrder(string Sku, int Quantity) : IRequest<CreateOrderResult>;

public sealed record CreateOrderResult(Guid OrderId, string Status);

public sealed class CreateOrderHandler : IRequestHandler<CreateOrder, CreateOrderResult>
{
    private readonly IOrderRepository _orders;

    public CreateOrderHandler(IOrderRepository orders)
    {
        _orders = orders;
    }

    public async ValueTask<CreateOrderResult> Handle(
        CreateOrder request,
        CancellationToken cancellationToken)
    {
        var order = new Order(request.Sku, request.Quantity);
        await _orders.Save(order, cancellationToken);

        return new CreateOrderResult(order.Id, "accepted");
    }
}

Use it from Minimal APIs:

app.MapPost("/orders", async (CreateOrder request, IMediator mediator, CancellationToken ct) =>
{
    var result = await mediator.Send(request, ct);
    return Results.Created($"/orders/{result.OrderId}", result);
});

Notifications

Notifications let one event fan out to multiple handlers. Use them for side effects such as audit logs, cache invalidation, email, metrics, and integration hooks.

public sealed record OrderCreated(Guid OrderId, string Sku) : INotification;

public sealed class WriteAuditLog : INotificationHandler<OrderCreated>
{
    public Task Handle(OrderCreated notification, CancellationToken cancellationToken)
    {
        // Persist an audit record.
        return Task.CompletedTask;
    }
}

public sealed class SendOrderConfirmation : INotificationHandler<OrderCreated>
{
    public Task Handle(OrderCreated notification, CancellationToken cancellationToken)
    {
        // Queue an email or integration message.
        return Task.CompletedTask;
    }
}

await mediator.Publish(new OrderCreated(order.Id, order.Sku), cancellationToken);

Publish behavior is configurable:

Strategy Behavior
Parallel Runs handlers concurrently. This is the default.
Sequential Runs all handlers in order and aggregates handler exceptions.
StopOnFirstException Runs handlers in order and stops at the first failure.

Streaming

Streaming requests are useful for progress updates, data feeds, incremental exports, long-running reads, and server-sent events.

using System.Runtime.CompilerServices;

public sealed record WatchOrder(Guid OrderId) : IStreamRequest<string>;

public sealed class WatchOrderHandler : IStreamRequestHandler<WatchOrder, string>
{
    public async IAsyncEnumerable<string> Handle(
        WatchOrder request,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        yield return "accepted";

        await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken);
        yield return "packed";

        await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken);
        yield return "shipped";
    }
}

Consume the stream:

await foreach (var status in mediator.CreateStream(new WatchOrder(orderId), cancellationToken))
{
    Console.WriteLine(status);
}

Pipeline Behaviors

Pipeline behaviors wrap request handling. They are ideal for logging, validation, metrics, authorization, caching, resilience, and transactions.

public sealed class LoggingBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
    }

    public async ValueTask<TResponse> Handle(
        TRequest request,
        CancellationToken cancellationToken,
        RequestHandlerDelegate<TResponse> next)
    {
        _logger.LogInformation("Handling {RequestType}", typeof(TRequest).Name);

        var response = await next();

        _logger.LogInformation("Handled {RequestType}", typeof(TRequest).Name);
        return response;
    }
}

Register open-generic behaviors:

builder.Services.AddConduit(options =>
{
    options.AddHandlersFromAssemblies(typeof(Program).Assembly);
    options.AddBehavior(typeof(LoggingBehavior<,>));
});

Validation

Add FluentValidation as a request pipeline behavior:

using ConduitR.Validation.FluentValidation;
using FluentValidation;

builder.Services.AddConduitValidation(typeof(Program).Assembly);

public sealed class CreateOrderValidator : AbstractValidator<CreateOrder>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.Sku).NotEmpty();
        RuleFor(x => x.Quantity).GreaterThan(0);
    }
}

Validation failures are raised before the handler runs.

ASP.NET Core

The ASP.NET Core add-on includes ProblemDetails support and Minimal API helper methods.

using ConduitR.AspNetCore;

builder.Services.AddConduitProblemDetails();

var app = builder.Build();

app.UseConduitProblemDetails();
app.MapMediatorPost<CreateOrder, CreateOrderResult>("/orders");

This keeps endpoints small while preserving standard HTTP error responses.

Pre/Post Processors

Use processors when you want simple before/after hooks without writing a full custom behavior.

using ConduitR.Processing;

builder.Services.AddConduitProcessing(typeof(Program).Assembly);

public sealed class AuditCreateOrder : IRequestPreProcessor<CreateOrder>
{
    public Task Process(CreateOrder request, CancellationToken cancellationToken)
    {
        // Record intent before the handler runs.
        return Task.CompletedTask;
    }
}

public sealed class MeasureCreateOrder : IRequestPostProcessor<CreateOrder, CreateOrderResult>
{
    public Task Process(
        CreateOrder request,
        CreateOrderResult response,
        CancellationToken cancellationToken)
    {
        // Record outcome after the handler succeeds.
        return Task.CompletedTask;
    }
}

Resilience

Add Polly-backed retry, timeout, and circuit-breaker behavior around request handlers:

using ConduitR.Resilience.Polly;

builder.Services.AddConduitResiliencePolly(options =>
{
    options.RetryCount = 3;
    options.Timeout = TimeSpan.FromSeconds(1);
    options.CircuitBreakerEnabled = true;
    options.CircuitBreakerFailures = 5;
    options.CircuitBreakerDuration = TimeSpan.FromSeconds(30);
});

Telemetry

ConduitR emits ActivitySource spans that plug into OpenTelemetry:

using ConduitR;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService("Orders.Api"))
    .WithTracing(tracing => tracing
        .AddSource(ConduitRTelemetry.ActivitySourceName)
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter());

Emitted operations:

Operation Description
Mediator.Send Request/response handling
Mediator.Publish Notification fan-out
Mediator.Stream Streaming request enumeration

Spans include request/response type tags, publish strategy information, handler counts where applicable, and exception events when failures occur.

Telemetry can be disabled for low-level benchmarks:

builder.Services.AddConduit(options =>
{
    options.AddHandlersFromAssemblies(typeof(Program).Assembly);
    options.EnableTelemetry = false;
});

Performance Notes

ConduitR is designed to keep the common path direct:

  • Send and stream invokers are cached per closed generic request/response pair.
  • Notification publish uses a cached generic invoker and avoids repeated enumeration.
  • Request handlers return ValueTask<T> to reduce allocations for synchronous completions.
  • Optional features live in separate packages, keeping the core small.

See docs/perf-pipeline-cache.md and the benchmarks/ folder for deeper performance notes.

Migrating From MediatR

MediatR ConduitR
IMediator.Send(...) IMediator.Send(...)
IMediator.Publish(...) IMediator.Publish(...)
IRequest<TResponse> IRequest<TResponse>
IRequestHandler<TRequest,TResponse> IRequestHandler<TRequest,TResponse>
INotification INotification
INotificationHandler<TNotification> INotificationHandler<TNotification>
IPipelineBehavior<TRequest,TResponse> IPipelineBehavior<TRequest,TResponse>
Pre/Post processors ConduitR.Processing
FluentValidation integration ConduitR.Validation.FluentValidation
ASP.NET Core helpers ConduitR.AspNetCore
Resilience behavior ConduitR.Resilience.Polly

Most request, handler, notification, and behavior concepts map directly. The main difference is that ConduitR keeps optional integrations in separate packages.

Visualizer And Visual Studio Tooling

ConduitR Visualizer is the design-time tooling layer for mediator-heavy applications. It helps you move from "where does this Send go?" to a concrete handler, pipeline, dependency list, and generated architecture artifact.

Use the tooling in two complementary ways:

  • CLI reports for architecture reviews, onboarding, CI artifacts, and pull request discussion.
  • Visual Studio analyzer hints for live handler discovery while reading endpoint or application code.

Install the Visualizer CLI when you want a generated architecture report for a solution or project:

dotnet tool install --global ConduitR.Visualizer.Cli --prerelease
conduitr visualize ./MyApp.sln --output ./artifacts/conduitr

The command writes:

artifacts/conduitr/
  flows.md
  flows.json
  diagrams/
    *.mmd

Add the analyzer package to a project when you want Visual Studio/Roslyn design-time hints:

dotnet add package ConduitR.Visualizer.Analyzers --prerelease

The analyzer reports which handler receives a mediator.Send(...) or mediator.CreateStream(...) request, and adds a lightbulb action that navigates to the handler without requiring a VSIX extension.

ConduitR Send handler hint

ConduitR CreateStream handler hint

Visualizer package details:

Samples And Docs

Versioning And Releases

ConduitR follows semantic versioning.

  • Latest stable: 1.0.4
  • Next release train: 1.0.5 with ConduitR Visualizer packages.
  • Tags like v1.0.4 publish stable NuGet packages.
  • Pushes to main publish prerelease packages such as 1.0.4-pre.<date>.<sha>.

See GitHub Releases and NuGet for published versions.

Development

dotnet restore
dotnet build
dotnet test

Run benchmarks:

dotnet run -c Release --project benchmarks/ConduitR.Benchmarks/ConduitR.Benchmarks.csproj

Contributing

Issues, suggestions, and pull requests are welcome. Keep changes focused, include tests for behavior changes, and update docs when public APIs or release behavior changes.

License

MIT. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 was computed.  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 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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on ConduitR:

Package Downloads
ConduitR.DependencyInjection

Microsoft.Extensions.DependencyInjection integration for ConduitR.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.6-pre.20260430T2242... 37 4/30/2026
1.0.6-pre.20260430T1645... 33 4/30/2026
1.0.5 160 4/30/2026
1.0.5-pre.20260430T0123... 38 4/30/2026
1.0.5-pre.20260429T0057... 41 4/29/2026
1.0.4 191 4/26/2026
1.0.4-pre.20260428T2205... 34 4/28/2026
1.0.4-pre.20260426T0508... 41 4/26/2026
1.0.4-pre.20260426T0157... 39 4/26/2026
1.0.4-pre.20260426T0153... 40 4/26/2026
1.0.4-pre.20260426T0131... 39 4/26/2026
1.0.4-pre.20260426T0126... 39 4/26/2026
1.0.4-pre.20260426T0104... 40 4/26/2026
1.0.4-pre.20260424T2216... 39 4/24/2026
1.0.4-pre.20250921T0523... 156 9/21/2025
1.0.3 3,658 9/21/2025
1.0.3-pre.20250921T0518... 152 9/21/2025
1.0.3-pre.20250921T0510... 149 9/21/2025
1.0.3-pre.20250814T0249... 166 8/14/2025
1.0.3-pre.20250813T2211... 157 8/13/2025
Loading failed