NetMediate 2026.6.2.793

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

NetMediate

CI/CD Pipeline Deploy Documentation NuGet NetMediate NuGet NetMediate.Core NuGet NetMediate.SourceGeneration

Quality Gate Status Bugs Code Smells Coverage Duplicated Lines (%) Lines of Code Reliability Rating Security Rating Technical Debt Maintainability Rating Vulnerabilities

License: MIT Documentation

A lightweight and efficient .NET implementation of the Mediator pattern for in-process messaging and communication between components.

Table of Contents

Introduction

NetMediate is a mediator pattern library for .NET that enables decoupled communication between components in your application. It provides a simple and flexible way to send commands, publish notifications, make requests, and handle streaming responses while maintaining clean architecture principles.

What’s new in this version

  • βœ… dotnet add package NetMediate.SourceGeneration is now the recommended entrypoint for application/startup projects.
  • πŸ“¦ NetMediate.Core now carries the contracts, while NetMediate.SourceGeneration injects NetMediate and GenDI.SourceGenerator through buildTransitive.
  • ✨ New generated typed dispatch extensions (for commands, notifications, requests, and streams) reduce boilerplate and improve call-site readability.
  • πŸ” buildTransitive propagation keeps generator behavior consistent in larger multi-project solutions when you intentionally allow transitive flow.

Why this improves day-to-day engineering

  • Faster onboarding: fewer setup decisions and less β€œit works on my machine” friction.
  • Cleaner organization: generated typed APIs make mediator usage explicit and easier to navigate in large solutions.
  • More predictable architecture: compile-time registration and transitive analyzer behavior keep projects aligned as teams scale.

Key Features

  • Commands: Send one-way messages to all registered handlers sequentially
  • Notifications: Publish messages to multiple handlers β€” all handlers started in parallel (Task.WhenAll); handler exceptions are logged but do not propagate to the caller (fire-and-forget). Batch notifications (IEnumerable) are also dispatched in parallel.
  • Requests: Send a message to a single handler and receive a typed response
  • Streaming: Handle requests that return multiple responses over time via IAsyncEnumerable
  • Pipeline Behaviors: Interceptors with pre/post flow for every message kind
  • Keyed handler routing: Register handlers under named keys and dispatch to specific subsets at runtime β€” fully NativeAOT + Trimming compatible via GenDI keyed-service resolution
  • Streaming fan-out: Multiple IStreamHandler registrations supported β€” their items are merged sequentially
  • Cancellation Support: Full cancellation token support across all operations
  • Broad runtime compatibility: Multi-targeted for net10.0, netstandard2.0, and netstandard2.1

Installation

Shared contracts project

Install-Package NetMediate.Core

Application / startup project

Install-Package NetMediate.SourceGeneration

Note: Install NetMediate.Core where you only need the contracts (IMediator, handlers, behaviors). Install NetMediate.SourceGeneration in the executable/startup project that calls AddNetMediate(). Its buildTransitive file adds the required PackageReference entries for NetMediate and GenDI.SourceGenerator.

.NET CLI

dotnet add package NetMediate.Core
dotnet add package NetMediate.SourceGeneration

Note: If you are publishing your own library, you may add PrivateAssets="all" to the NetMediate.SourceGeneration reference to avoid flowing the generator package transitively. The startup project can keep the default behavior.

PackageReference

<PackageReference Include="NetMediate.Core" Version="x.x.x" />
<PackageReference Include="NetMediate.SourceGeneration" Version="x.x.x.x">
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  <PrivateAssets>contentfiles; compile; runtime</PrivateAssets>
</PackageReference>

Note: NetMediate.SourceGeneration should be referenced with IncludeAssets + PrivateAssets="contentfiles; compile; runtime" so analyzers/source generators continue flowing transitively where needed.

GenDI-first activation pattern

NetMediate.SourceGeneration also activates GenDI in the startup project. Prefer the GenDI style for your application services and supporting implementations:

using GenDI;
using Microsoft.Extensions.DependencyInjection;

[ServiceInjection]
public interface IEmailService
{
    Task SendWelcomeEmailAsync(string email, CancellationToken cancellationToken);
}

[Injectable(ServiceLifetime.Scoped, Group = 10, Order = 1, Key = "primary")]
public sealed class SmtpEmailService : IEmailService
{
    public Task SendWelcomeEmailAsync(string email, CancellationToken cancellationToken) =>
        Task.CompletedTask;
}

[Injectable(ServiceLifetime.Scoped)]
public sealed class UserFacade
{
    [Inject] public required IEmailService EmailService { get; init; }
    [Inject] public required ILogger<UserFacade> Logger { get; init; }
}

With GenDI the consumer chooses the ServiceLifetime, Group, Order, and Key. Use [Injectable<TService>] only when you need to force a specific non-generic contract and contract discovery does not already find [ServiceInjection]. Concrete non-generic classes that implement closed generic contracts can still use [Injectable]. Only generic/open service implementations (for example AuditBehavior<TMessage, TResponse>) should be registered manually in builder.Services for the AOT-oriented path. AddNetMediate() already calls AddGenDIServices() for you.

Optional companion packages

<PackageReference Include="NetMediate.Moq" Version="x.x.x" />
  • NetMediate.Moq: lightweight Moq helpers for unit and integration tests (Mocking.Create, AddMockSingleton, async setup extensions).

Companion Guides

Quick Start

Here's a minimal example to get you started with NetMediate:

using GenDI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NetMediate;

public record UserCreated(string UserId, string Email);

[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 1)]
public class UserCreatedHandler : INotificationHandler<UserCreated>
{
    [Inject] public required ILogger<UserCreatedHandler> Logger { get; init; }

    public Task Handle(UserCreated notification, CancellationToken cancellationToken = default)
    {
        Logger.LogInformation("User {UserId} was created", notification.UserId);
        return Task.CompletedTask;
    }
}

public static class QuickStartExample
{
    public static async Task RunAsync()
    {
        // 1. Install the package
        // Shared contracts: dotnet add package NetMediate.Core
        // Startup/app project: dotnet add package NetMediate.SourceGeneration

        // 2. Register services β€” source generator discovers all handlers automatically
        var builder = Host.CreateApplicationBuilder();
        builder.Services.AddNetMediate(); // all handlers in your project are registered here

        // 3. Use the mediator
        var host = builder.Build();
        await host.StartAsync();
        var mediator = host.Services.GetRequiredService<IMediator>();
        await mediator.NotifyUserCreatedAsync(new("123", "user@example.com"));
    }
}

For more detailed examples, see the Usage Examples section below.

Usage Examples

Basic Setup

Register NetMediate services using the source generator:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NetMediate;

var builder = Host.CreateApplicationBuilder();

// NetMediate.SourceGeneration discovers handlers automatically at compile time
// and registers all handlers in your project.
builder.Services.AddNetMediate();

var host = builder.Build();
var mediator = host.Services.GetRequiredService<IMediator>();

Notifications

Notify is fire-and-forget β€” the pipeline task is discarded and Task.CompletedTask is returned immediately to the caller. All registered handlers are started concurrently; handler and behavior exceptions are all logged by the executor but do not propagate to the caller. When sending a batch of notifications (IEnumerable), each message's pipeline is dispatched sequentially in a loop.

Define a Notification Message
// No marker interface required β€” any plain class or record works
public record UserRegistered(string UserId, string Email, DateTime RegisteredAt);
Create Notification Handlers
[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 1)]
public class EmailNotificationHandler : INotificationHandler<UserRegistered>
{
    [Inject] public required IEmailService EmailService { get; init; }

    // Handle must return Task, not Task
    public async Task Handle(UserRegistered notification, CancellationToken cancellationToken = default)
    {
        await EmailService.SendWelcomeEmailAsync(notification.Email, cancellationToken);
    }
}

[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 2)]
public class AuditLogHandler : INotificationHandler<UserRegistered>
{
    [Inject] public required IAuditService AuditService { get; init; }

    public async Task Handle(UserRegistered notification, CancellationToken cancellationToken = default)
    {
        await AuditService.LogEventAsync(
            $"User {notification.UserId} registered",
            cancellationToken
        );
    }
}
Publish Notifications
var notification = new UserRegistered("user123", "user@example.com", DateTime.UtcNow);
await mediator.NotifyUserRegisteredAsync(notification, cancellationToken);

Batch notifications in one call:

var notifications = new[]
{
    new UserRegistered("user123", "user@example.com", DateTime.UtcNow),
    new UserRegistered("user321", "user2@example.com", DateTime.UtcNow)
};
await mediator.NotifyUserRegisteredAsync(notifications, cancellationToken);

Commands

Commands are dispatched to all registered handlers sequentially (one after another in registration order). Use Send when you want to trigger a side-effect across multiple consumers with no return value.

Define a Command
// No marker interface required β€” any plain class or record works
public record CreateUserCommand(string Email, string FirstName, string LastName);
Create a Command Handler

Multiple handlers can be registered for the same command type β€” all run sequentially on each Send call.

[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 1)]
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand>
{
    [Inject] public required IUserRepository UserRepository { get; init; }

    // Handle must return Task
    public async Task Handle(CreateUserCommand command, CancellationToken cancellationToken = default)
    {
        var user = new User
        {
            Email = command.Email,
            FirstName = command.FirstName,
            LastName = command.LastName
        };

        await UserRepository.CreateAsync(user, cancellationToken);
    }
}
Send Commands
var command = new CreateUserCommand("user@example.com", "John", "Doe");
await mediator.SendCreateUserCommandAsync(command);

Requests

Requests are sent to a handler and return a response.

Define a Request and Response
// No marker interface required
public record GetUserQuery(string UserId);
public record UserDto(string Id, string Email, string FirstName, string LastName);
Create a Request Handler
[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 1)]
public class GetUserQueryHandler : IRequestHandler<GetUserQuery, UserDto>
{
    [Inject] public required IUserRepository UserRepository { get; init; }

    // Handle must return Task<TResponse>
    public async Task<UserDto> Handle(GetUserQuery query, CancellationToken cancellationToken = default)
    {
        var user = await UserRepository.GetByIdAsync(query.UserId, cancellationToken);

        return new UserDto(user.Id, user.Email, user.FirstName, user.LastName);
    }
}
Send Requests
var query = new GetUserQuery("user123");
var userDto = await mediator.RequestGetUserQueryAsync(query);

Streams

Streams allow handlers to return multiple responses over time.

Define a Stream Request
// No marker interface required
public record GetUserActivityQuery(string UserId, DateTime FromDate);
public record ActivityDto(string Id, string Action, DateTime Timestamp);
Create a Stream Handler
[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 1)]
public class GetUserActivityQueryHandler : IStreamHandler<GetUserActivityQuery, ActivityDto>
{
    [Inject] public required IActivityRepository ActivityRepository { get; init; }
    
    public async IAsyncEnumerable<ActivityDto> Handle(
        GetUserActivityQuery query, 
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        await foreach (var activity in ActivityRepository.GetUserActivityStreamAsync(
            query.UserId, query.FromDate, cancellationToken))
        {
            yield return new ActivityDto(activity.Id, activity.Action, activity.Timestamp);
        }
    }
}
Process Streams
var query = new GetUserActivityQuery("user123", DateTime.UtcNow.AddDays(-30));

await foreach (var activity in mediator.StreamGetUserActivityQueryAsync(query))
{
    Console.WriteLine($"{activity.Timestamp}: {activity.Action}");
}

Message type summary

NetMediate messages are plain records or classes β€” no marker interfaces are required. The message type and the handler type are always separate.

Message kind Handler interface Dispatch semantics
Command ICommandHandler<TMessage> All registered handlers, sequential in registration order
Request IRequestHandler<TMessage, TResponse> First registered handler only; returns TResponse
Notification INotificationHandler<TMessage> All handlers started in parallel (fire-and-forget); handler exceptions are logged
Stream IStreamHandler<TMessage, TResponse> All registered handlers, items merged sequentially (handler A items first, then handler B)
// Command β€” no return value, dispatched to all registered handlers sequentially
public record DeleteUserCommand(string UserId);

// Request β€” single handler, returns a response
public record GetUserQuery(string UserId);

// Notification β€” all handlers started in parallel (fire-and-forget); handler exceptions unobserved
public record UserDeleted(string UserId);

// Stream β€” all registered handlers, items merged sequentially
public record GetRecentEventsQuery(int MaxItems);

Keyed Dispatch

Register handlers under routing keys and dispatch to a specific subset at runtime. This is useful for scenarios such as queue/topic routing, tenant isolation, or environment-specific handling:

[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 1)]
public sealed class DefaultHandler : ICommandHandler<MyCommand>
{
    public Task Handle(MyCommand message, CancellationToken cancellationToken = default) =>
        Task.CompletedTask;
}

[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 2, Key = "audit")]
public sealed class AuditHandler : ICommandHandler<MyCommand>
{
    public Task Handle(MyCommand message, CancellationToken cancellationToken = default) =>
        Task.CompletedTask;
}

builder.Services.AddNetMediate();

// Dispatch to null-key (default) handlers
await mediator.SendMyCommandAsync(new MyCommand(), cancellationToken);

// Dispatch only to "audit" handlers
await mediator.SendMyCommandAsync("audit", new MyCommand(), cancellationToken);

The key is propagated through the entire pipeline β€” behaviors receive it in their Handle(object? key, ...) signature and can use it for routing, logging, or conditional logic.

Keyless dispatch: A null key (the default when no key is passed) flows through the pipeline unchanged. mediator.SendMyCommandAsync(command, ct) and mediator.SendMyCommandAsync(null, command, ct) are exactly equivalent and target the non-keyed handlers registered in the container.

NativeAOT: Keyed dispatch is fully NativeAOT + Trimming compatible. GenDI resolves keyed services; NetMediate dispatch uses GetKeyedServices/GetRequiredKeyedService at runtime. Both keyed and non-keyed dispatch are safe for NativeAOT and trimmed deployments.

Pipeline Behaviors / Interceptors

Pipeline composition is now static and based on GenDI decorators. Use DecoratorForAttribute on handlers to implement cross-cutting concerns.

[DecoratorFor<ICommandHandler<CreateUserCommand>>(Order = 1)]
public sealed class AuditCommandDecorator(ICommandHandler<CreateUserCommand> inner)
    : ICommandHandler<CreateUserCommand>
{
    public Task Handle(
        CreateUserCommand message,
        CancellationToken cancellationToken = default)
    {
        // pre
        return inner.Handle(message, cancellationToken);
        // post (use async/await if needed)
    }
}

[DecoratorFor<IRequestHandler<GetUserQuery, UserDto>>(Order = 2)]
public sealed class AuditRequestDecorator(IRequestHandler<GetUserQuery, UserDto> inner)
    : IRequestHandler<GetUserQuery, UserDto>
{
    public async Task<UserDto> Handle(
        GetUserQuery message,
        CancellationToken cancellationToken = default)
    {
        var startedAt = DateTimeOffset.UtcNow;
        var response = await inner.Handle(message, cancellationToken);
        Console.WriteLine($"{nameof(GetUserQuery)} handled in {DateTimeOffset.UtcNow - startedAt}");
        return response;
    }
}

[DecoratorFor<INotificationHandler<UserCreatedNotification>>(Order = 3)]
public sealed class LogNotificationDecorator(INotificationHandler<UserCreatedNotification> inner)
    : INotificationHandler<UserCreatedNotification>
{
    public async Task Handle(
        UserCreatedNotification message,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"Dispatching {nameof(UserCreatedNotification)}");
        await inner.Handle(message, cancellationToken);
        Console.WriteLine($"Dispatched {nameof(UserCreatedNotification)}");
    }
}

builder.Services.AddNetMediate();

Framework Support

Supported package TFMs

All runtime packages are published with:

  • net10.0
  • netstandard2.0
  • netstandard2.1

NetMediate.SourceGeneration is shipped as its own package (netstandard2.0 analyzer). When installed directly, its buildTransitive file adds the required NetMediate runtime and GenDI.SourceGenerator dependencies automatically.

Application types covered

Because packages expose netstandard2.0 and netstandard2.1 assets they can be consumed by desktop, CLI, mobile, MAUI, and server/web applications.

Contributing

Contributions are welcome! Please read our Contributing Guidelines and Code of Conduct.

License

This project is licensed under the MIT License - see the LICENSE file for details.

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 (3)

Showing the top 3 NuGet packages that depend on NetMediate:

Package Downloads
NetMediate.Moq

Utilities for elegant and efficient NetMediate unit and integration test mocking.

NetMediate.Resilience

Resilience pipeline behaviors for NetMediate (retry, timeout, circuit breaker).

NetMediate.Quartz

Quartz.NET-backed persistent notification scheduler for NetMediate. Enables crash recovery and cluster-distributed notification execution.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.6.2.793 0 6/2/2026
2026.6.1.809 55 6/1/2026
2026.5.29.881 60 5/29/2026
2026.5.29.822 54 5/29/2026
2026.5.25.1179 129 5/25/2026
2026.5.25.1106 119 5/25/2026
2026.5.25.1091 117 5/25/2026
2026.5.25.1029 119 5/25/2026
2026.5.25.1017 117 5/25/2026
2026.5.25.987 129 5/25/2026
2026.5.25.971 124 5/25/2026
2026.5.22.800 149 5/22/2026
2026.5.22.798 126 5/22/2026
2026.5.21.1216 129 5/21/2026
2026.5.19.1340 151 5/19/2026
2026.5.17.1060 149 5/17/2026
2026.5.16.984 164 5/16/2026
2026.5.16.823 131 5/16/2026
2026.5.16.787 130 5/16/2026
2026.5.15.1272 140 5/15/2026
Loading failed