PicoMediator 2026.10.0

dotnet add package PicoMediator --version 2026.10.0
                    
NuGet\Install-Package PicoMediator -Version 2026.10.0
                    
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="PicoMediator" Version="2026.10.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PicoMediator" Version="2026.10.0" />
                    
Directory.Packages.props
<PackageReference Include="PicoMediator" />
                    
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 PicoMediator --version 2026.10.0
                    
#r "nuget: PicoMediator, 2026.10.0"
                    
#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 PicoMediator@2026.10.0
                    
#: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=PicoMediator&version=2026.10.0
                    
Install as a Cake Addin
#tool nuget:?package=PicoMediator&version=2026.10.0
                    
Install as a Cake Tool

PicoMediator

Compile-time command/event dispatch for PicoDI. Zero reflection, AOT-first.

Requirements: .NET 10+ — the PicoMediator runtime and ALL generated code (dispatch switches, handler registrations, base-type bridges) require net10.0+ consumers. PicoMediator.Abs targets net10.0; only the PicoMediator.Gen analyzer stays on netstandard2.0 (Roslyn loader compatibility) and it ships embedded in PicoMediator.Abs.

Quick Start

dotnet add package PicoMediator
using PicoDI;
using PicoMediator;
using PicoMediator.Abs;

var container = new SvcContainer();

// Declare-and-subscribe: PicoMediator.Gen scans handler implementations
// and AddPicoMediator() registers them automatically — no manual wiring.
container.AddPicoMediator();
container.Build();

await using var scope = container.CreateScope();
var mediator = scope.GetService<IMediator>();

// Send: 1:1 command → response
var result = await mediator.Send<Ping, string>(new Ping());

// Publish: 1:N event → all subscribers
await mediator.Publish(new OrderCreated(Guid.NewGuid(), "book"));

Handler implementations are discovered automatically:

public record Ping(string Message) : ICommand<string>;

public sealed class PingHandler : ICommandHandler<Ping, string>
{
    public ValueTask<string> Handle(Ping c, CancellationToken ct) => new($"pong:{c.Message}");
}

Core Concepts

Interaction Patterns

PicoMediator follows the ZeroMQ-inspired principle: interaction patterns are atomic primitives encoded by the type system.

Pattern ZeroMQ C# Type Cardinality Response
Command REQ/REP ICommand<TResponse> 1:1 Yes
Event PUB/SUB IEvent 1:N No

Both derive from IMessage. No IQuery split — a query is a command whose result is a read. Define your own via interface inheritance if needed:

public interface IQuery<T> : ICommand<T> { }

No IStreamRequest — wrap IAsyncEnumerable<T> in the response:

public record ExportUsers : ICommand<ExportUsersResponse>;
public record ExportUsersResponse(IAsyncEnumerable<User> Users);

Void Commands

Use VoidResult (from PicoDI.Abs) for fire-and-forget commands:

public record DeleteOrder(Guid Id) : ICommand<VoidResult>;

public class DeleteOrderHandler : ICommandHandler<DeleteOrder, VoidResult>
{
    public async ValueTask<VoidResult> Handle(DeleteOrder r, CancellationToken ct)
    {
        await DeleteAsync(r.Id, ct);
        return default;
    }
}

Publisher/Subscriber Semantics

Publish follows ZeroMQ PUB/SUB semantics:

  • Publisher does not know subscribers
  • No return value (protocol forbids it)
  • No subscribers → silent drop (not an error)
  • Multiple subscribers → each receives the event
// 2 subscribers
public sealed class EmailHandler : ISubscriber<OrderCreated> { ... }
public sealed class AuditHandler : ISubscriber<OrderCreated> { ... }

await mediator.Publish(new OrderCreated(id, item));
// → EmailHandler.Handle() called
// → AuditHandler.Handle() called

Base-Type Publish

Publishing through a base-typed variable reaches concrete subscribers via generated bridge subscribers (PicoMediator.Gen scans all IEvent implementations and their inheritance; AddPicoMediator() registers one bridge per non-concrete base type):

IReadOnlyList<IEvent> events = [new OrderPaid(1), new OrderShipped(2)];
foreach (var e in events)
    await mediator.Publish(e);   // delivered to ISubscriber<OrderPaid> / ISubscriber<OrderShipped>

Contract of base-typed Publish<T>: base-key direct subscribers + all concrete subscribers of the runtime type. Documented deltas vs exact-type publish: PublishParallel<Base> forwards sequentially inside the bridge; OnNoSubscribers does not fire for base-typed publishes; base-declared subscribers (ISubscriber<IEvent>) do NOT receive concrete-typed publishes. The generator warns (PMGEN001) at call sites where a base type has no known concrete event types. Both class and record struct events are supported (boxed dispatch through the bridge). No xxUntyped APIs exist by design — dispatch is compile-time-table based, AOT-safe, zero reflection.

Registration

Declare-and-Subscribe (primary path)

PicoMediator.Gen scans all closed, non-abstract ICommandHandler<T, R> / ISubscriber<T> implementations in your assembly. AddPicoMediator() applies them as Transient registrations:

container.AddPicoMediator(); // auto-registers all scanned handlers
  • Multi-assembly: handlers in referenced library assemblies are included — every assembly that references PicoMediator.Abs (which embeds the PicoMediator.Gen analyzer) contributes its own scanned handlers, and AddPicoMediator() applies configurators from all loaded assemblies (the "library of handlers" pattern).
  • Constructor dependencies are resolved from the container (typed, zero reflection).
  • One class implementing several handler interfaces yields one registration per interface.
  • Open-generic handler classes are skipped (register closed forms manually).

Manual Registration (override path)

Manual registrations made before AddPicoMediator() win over generated ones (dedup is per service type):

// Custom lifetime or instance — manual wins, generator skips this service type
container.RegisterSingle<ISubscriber<OrderCreated>>(new EmailHandler());
container.AddPicoMediator();

To disable auto-registration entirely:

container.AddPicoMediator(autoRegisterHandlers: false);

Mediator

One line — registers IMediator as Scoped:

container.AddPicoMediator();

Scoped is the default for request-scoped isolation. Use AddPicoMediator(SvcLifetime.Singleton) for a stateless mediator.

Narrow Ports

Depend on the narrowest interface for your component:

// Only sends commands
public sealed class OrderController(IRequester requester) { ... }

// Only publishes events
public sealed class EventSource(IPublisher publisher) { ... }

// Orchestration — needs both
public sealed class CheckoutService(IMediator mediator) { ... }

Rename Map

The REQ/REP + PUB/SUB pattern vocabulary replaced the old protocol markers:

Old New
IRequest<TResponse> ICommand<TResponse>
IRequestHandler<T, R> ICommandHandler<T, R>
INotification IEvent
INotificationHandler<T> ISubscriber<T>
ISender IRequester

IPublisher / IMediator / VoidResult are unchanged. IMessage is the new root marker for both ICommand<TResponse> and IEvent.

Pipeline Behaviors (via PicoAop)

PicoMediator does NOT define its own pipeline abstraction. Use PicoAop interceptors instead:

// Mediator-level — applies to all Send/Publish calls
container.Register<IMediator, Mediator>(SvcLifetime.Scoped)
    .InterceptBy<MetricsInterceptor>();

// Handler-level — applies to a specific command type
container.Register<ICommandHandler<CreateOrder, OrderResult>, CreateOrderHandler>(SvcLifetime.Transient)
    .InterceptBy<LoggingInterceptor>()
    .InterceptBy<ValidationInterceptor>()
    .InterceptBy<TransactionInterceptor>();

// Decorator chain (onion model):
// Transaction → Validation → Logging → Handler

Source Generator (PicoMediator.Gen)

The generator is embedded in PicoMediator.Abs (no extra package) — reference PicoMediator.Abs and it activates automatically.

The generator scans ICommandHandler<T, R> / ISubscriber<T> implementations and emits:

  • MediatorSwitch.g.cs — switch-based typed Send dispatch (fully-qualified, non-generic resolution; compiles in non-friend assemblies with no using PicoDI.Abs dependency)
  • MediatorHandlerRegistrations_<assembly>.g.cs — the declare-and-subscribe registrations applied by AddPicoMediator()
  • Runtime fallback — scope.GetService<T>() for handlers not in the switch table

Without the generator, Mediator.Send() still works via the runtime GetService fallback, but handlers must be registered manually.

Error Handling

Scenario Behavior
Send — no handler registered InvalidOperationException
Send — handler throws Exception propagates to caller
Publish — no subscribers Silent (PUB/SUB semantics)
Publish — one handler throws Exception propagates to caller
Publish — multiple handlers fail AggregateException
Publish / PublishParallel — handler throws OperationCanceledException Propagates as-is — cancellation is never wrapped in AggregateException

Packages

Package Description
PicoMediator.Abs IMessage, ICommand<T>, IEvent, ICommandHandler<T, T>, ISubscriber<T>, IRequester, IPublisher, IMediator
PicoMediator Mediator(ISvcScope) runtime, GeneratedDispatch, MediatorAutoSubscriptionRegistry
PicoMediator.Gen Source generator — switch dispatch + handler registrations (embedded in PicoMediator.Abs)
PicoMediator.DI container.AddPicoMediator()

← Back to PicoInfra

Product 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. 
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 PicoMediator:

Package Downloads
PicoMediator.DI

PicoDI registration extension for PicoMediator

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.10.0 247 9/18/2026
2026.9.3 147 9/15/2026
2026.9.2 100 9/14/2026
2026.9.1 140 8/24/2026
2026.9.0 148 8/23/2026
2026.8.9 112 8/22/2026
2026.8.8 113 8/22/2026
2026.8.7 122 8/21/2026
2026.8.6 121 8/21/2026
2026.8.5 126 8/16/2026
2026.8.4 121 8/13/2026
2026.8.3 119 8/8/2026
2026.8.2 158 8/5/2026
2026.8.1 119 8/5/2026
2026.8.0 122 8/5/2026
2026.7.3 136 7/12/2026
2026.7.2 127 7/12/2026
2026.7.1 128 7/12/2026
2026.6.17 133 7/12/2026
2026.6.16 136 7/9/2026
Loading failed