ChannelMediator.MinimalApiGenerator 1.3.0

dotnet add package ChannelMediator.MinimalApiGenerator --version 1.3.0
                    
NuGet\Install-Package ChannelMediator.MinimalApiGenerator -Version 1.3.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="ChannelMediator.MinimalApiGenerator" Version="1.3.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ChannelMediator.MinimalApiGenerator" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="ChannelMediator.MinimalApiGenerator">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
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 ChannelMediator.MinimalApiGenerator --version 1.3.0
                    
#r "nuget: ChannelMediator.MinimalApiGenerator, 1.3.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 ChannelMediator.MinimalApiGenerator@1.3.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=ChannelMediator.MinimalApiGenerator&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=ChannelMediator.MinimalApiGenerator&version=1.3.0
                    
Install as a Cake Tool

๐Ÿš€ ChannelMediator

NuGet ChannelMediator NuGet ChannelMediator.Contracts NuGet ChannelMediator.AzureBus NuGet ChannelMediator.RabbitMQ NuGet ChannelMediator.ApiGenerators.Abstraction NuGet ChannelMediator.MinimalApiGenerator NuGet ChannelMediator.ApiClientGenerator NuGet ChannelMediator.GrpcGenerator NuGet ChannelMediator.GrpcClientGenerator Build .NET C#

A modern, high-performance mediator for .NET, built on System.Threading.Channels, with full MediatR compatibility.

Compatible with .NET 8, .NET 9, and .NET 10.

โœจ Features

  • โœ… MediatR Compatible - Familiar API (Send / Publish / CreateStream)
  • โœ… Channel-Based - Asynchronous processing with natural backpressure
  • โœ… Pipeline Behaviors - Global AND specific
  • โœ… Streaming - IAsyncEnumerable<T> with IStreamRequest<T> and stream pipeline behaviors
  • โœ… Parallel Notifications - Sequential or parallel broadcasting
  • โœ… High Performance - Channel-based with modern optimizations
  • โœ… Azure Service Bus - Distributed messaging with queues and topics
  • โœ… RabbitMQ - Self-hosted distributed messaging with exchanges and queues
  • โœ… Minimal API Generator - Source-generated endpoint mapping from request attributes
  • โœ… API Client Generator - Source-generated HttpClient handlers for consuming generated APIs
  • โœ… gRPC Generator - Source-generated code-first gRPC services via protobuf-net.Grpc
  • โœ… gRPC Client Generator - Source-generated IRequestHandler calling gRPC services
  • โœ… .NET 8 / 9 / 10 - Multi-targeted packages for current .NET versions

๐Ÿ“ฆ Installation

# Package (coming soon)
dotnet add package ChannelMediator

# Or local reference
<ProjectReference Include="..\ChannelMediator\ChannelMediator.csproj" />

๐ŸŽฏ Quick Start

Configuration

using ChannelMediator;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

var services = new ServiceCollection();

// Register the mediator
services.AddChannelMediator(
    config => config.Strategy = NotificationPublishStrategy.Parallel,
    Assembly.GetExecutingAssembly());

var provider = services.BuildServiceProvider();
var mediator = provider.GetRequiredService<IMediator>();

If multiple handlers are found for the same request, only the first registered handler is kept and the others are ignored. When you pass multiple assemblies to AddChannelMediator, the declaration order determines which handler is selected by default. Put the assembly containing your preferred default handler first.

services.AddChannelMediator(
    assemblies: [
        typeof(MyPreferredHandler).Assembly,
        typeof(MyFallbackHandler).Assembly
    ]);

Define a Request

// Request
public record AddToCartRequest(string ProductCode) : IRequest<CartItem>;

// Response
public record CartItem(string ProductCode, int Quantity, decimal Total);

// Handler
public class AddToCartHandler : IRequestHandler<AddToCartRequest, CartItem>
{
    public async Task<CartItem> Handle(
        AddToCartRequest request, 
        CancellationToken cancellationToken)
    {
        // Business logic
        return new CartItem(request.ProductCode, 1, 19.99m);
    }
}

Usage

var cart = await mediator.Send(new AddToCartRequest("ABC123"));

Notifications

// Notification
public record ProductAddedNotification(string ProductCode, int Quantity) : INotification;

// Handlers (multiple handlers supported)
public class LogHandler : INotificationHandler<ProductAddedNotification>
{
    public Task Handle(ProductAddedNotification notification, CancellationToken ct)
    {
        Console.WriteLine($"LOG: {notification.ProductCode}");
        return Task.CompletedTask;
    }
}

public class EmailHandler : INotificationHandler<ProductAddedNotification>
{
    public async Task Handle(ProductAddedNotification notification, CancellationToken ct)
    {
        await SendEmailAsync(notification.ProductCode);
    }
}

// Publish notification to all handlers
await mediator.Publish(new ProductAddedNotification("ABC123", 1));

๐ŸŽญ Pipeline Behaviors

Global Behaviors (for ALL requests)

public class LoggingBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>, IPipelineBehavior
    where TRequest : IRequest<TResponse>
{
    public async ValueTask<TResponse> HandleAsync(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        Console.WriteLine($"Before: {typeof(TRequest).Name}");
        var response = await next();
        Console.WriteLine($"After: {typeof(TRequest).Name}");
        return response;
    }
}

// Registration
services.AddOpenPipelineBehavior(typeof(LoggingBehavior<,>));
services.AddOpenPipelineBehavior(typeof(PerformanceMonitoringBehavior<,>));

Specific Behaviors (for a specific request type)

public class ValidationBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    public async ValueTask<TResponse> HandleAsync(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        // Specific validation
        if (request is AddToCartRequest { ProductCode: null or "" })
            throw new ArgumentException("ProductCode required");
            
        return await next();
    }
}

// Registration
services.AddPipelineBehavior<AddToCartRequest, CartItem, ValidationBehavior<AddToCartRequest, CartItem>>();

๐ŸŒŠ Streaming

Stream large or incremental datasets with IStreamRequest<T> and IAsyncEnumerable<T>. Streaming requests bypass the channel pump and are dispatched directly, so they don't block other requests.

Define a Stream Request

public record GetOrderLinesQuery(int OrderId) : IStreamRequest<OrderLineDto>;

public class GetOrderLinesHandler : IStreamRequestHandler<GetOrderLinesQuery, OrderLineDto>
{
    private readonly IOrderRepository _repo;

    public GetOrderLinesHandler(IOrderRepository repo) => _repo = repo;

    public async IAsyncEnumerable<OrderLineDto> Handle(
        GetOrderLinesQuery request,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        await foreach (var line in _repo.StreamOrderLinesAsync(request.OrderId, cancellationToken))
        {
            yield return line;
        }
    }
}

Usage

await foreach (var line in mediator.CreateStream(new GetOrderLinesQuery(42), cancellationToken))
{
    Console.WriteLine(line);
}

Stream Pipeline Behaviors

public class StreamLoggingBehavior<TRequest, TResponse>
    : IStreamPipelineBehavior<TRequest, TResponse>
    where TRequest : IStreamRequest<TResponse>
{
    public async IAsyncEnumerable<TResponse> Handle(
        TRequest request,
        StreamHandlerDelegate<TResponse> next,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        Console.WriteLine($"Stream starting: {typeof(TRequest).Name}");
        await foreach (var item in next().WithCancellation(cancellationToken))
        {
            yield return item;
        }
        Console.WriteLine($"Stream completed: {typeof(TRequest).Name}");
    }
}

// Registration
services.AddScoped<IStreamPipelineBehavior<GetOrderLinesQuery, OrderLineDto>,
    StreamLoggingBehavior<GetOrderLinesQuery, OrderLineDto>>();

๐Ÿ“Š Available APIs

Method Return Type Description
Send<TResponse>(IRequest<TResponse>, CancellationToken) Task<TResponse> Sends a request to a single handler and returns the response
Send(IRequest, CancellationToken) Task Sends a request without response (command)
Publish<TNotification>(TNotification, CancellationToken) Task Publishes a notification to multiple handlers
CreateStream<TResponse>(IStreamRequest<TResponse>, CancellationToken) IAsyncEnumerable<TResponse> Creates an async stream from a streaming handler

๐Ÿ“š Documentation

๐Ÿ”Œ API & gRPC Generators

ChannelMediator provides Roslyn source generators that eliminate boilerplate for ASP.NET Core Minimal APIs, their HTTP clients, and code-first gRPC services.

The shared attributes package was renamed from ChannelMediator.MinimalApiGenerator.Abstraction to ChannelMediator.ApiGenerators.Abstraction to reflect its broader scope (HTTP + gRPC).

HTTP โ€” Auto-generate Minimal API endpoints

Decorate request contracts with [EndpointApi] (default Protocol = EndpointProtocol.Http) and a mapper class with [MapApiExtension].

// 1. Shared contracts project โ€” install ChannelMediator.ApiGenerators.Abstraction
[EndpointApi(GroupName = "Catalog", Path = "products", UseHttpStandardVerbs = true)]
public record GetProductRequest(int Id) : IRequest<Product?>;

[EndpointApi(GroupName = "Catalog", Path = "products")]
public record SaveProductRequest(Product Product) : IRequest<Product>;

// 2. Server project โ€” install ChannelMediator.MinimalApiGenerator
[MapApiExtension]
public static partial class MyHttpMapper { }

// 3. Program.cs
app.MapMyHttpMapper(); // All HTTP endpoints registered!

HTTP Client โ€” Auto-generate HttpClient handlers

// Client project โ€” install ChannelMediator.ApiClientGenerator
[assembly: ApiClient(typeof(GetProductRequest), HttpClientName = "ApiClient")]

services.AddHttpClient("ApiClient")
    .ConfigureHttpClient(cfg => cfg.BaseAddress = new Uri("https://localhost:7031/api/"));

var product = await mediator.Send(new GetProductRequest(1));

gRPC โ€” Auto-generate code-first gRPC services

Set Protocol = EndpointProtocol.Grpc (or Both) on requests, then decorate a mapper with [GrpcServiceExtension].

// Shared contracts โ€” set Grpc or Both on the protocol
[EndpointApi(GroupName = "Catalog", Path = "products", Protocol = EndpointProtocol.Both)]
public record GetProductRequest(int Id) : IRequest<Product?>;

// Server project โ€” install ChannelMediator.GrpcGenerator
[GrpcServiceExtension]
public static partial class MyGrpcMapper { }

// Program.cs
app.MapMyGrpcMapperGrpcServices(); // Registers gRPC service

gRPC Client โ€” Auto-generate gRPC client handlers

// Client project โ€” install ChannelMediator.GrpcClientGenerator
[assembly: GrpcClient(typeof(GetProductRequest), GrpcClientName = "GrpcClient")]

services.AddGrpcClient<ICatalogService>("GrpcClient")
    .ConfigureChannel(o => o.Address = new Uri("https://localhost:7032"));

var product = await mediator.Send(new GetProductRequest(1));

See โšก Generators documentation for the full reference.

๐Ÿ‘‰ Full documentation โ†’

๐Ÿ—๏ธ Architecture

Client
  โ†“
IMediator (Send / Publish)
  โ†“
Channel (async queue)
  โ†“
RequestHandlerWrapper
  โ†“
Pipeline Behaviors (chain)
  โ”œโ”€ Global Behavior 1
  โ”œโ”€ Global Behavior 2
  โ”œโ”€ Specific Behavior 1
  โ””โ”€ Request Handler (business logic)

๐ŸšŒ Azure Service Bus Integration

In a microservice architecture, a single process cannot handle all requests. You need to distribute workloads across multiple consumer instances and decouple services through asynchronous messaging.

ChannelMediator.AzureBus extends the mediator with two extension methods that transparently route messages through Azure Service Bus:

  • mediator.Notify(notification) โ†’ Publishes to a Topic (fan-out to all subscribers)
  • mediator.EnqueueRequest(request) โ†’ Enqueues to a Queue (competing consumers, only one processes each message)
var mediator = app.Services.GetRequiredService<IMediator>();

// Fan-out notification to all subscriber services
await mediator.Notify(new ProductAddedNotification("SKU-001", 5, 49.95m));

// Enqueue a request for competing consumer processing
await mediator.EnqueueRequest(new MyRequest("process-order-42"));

Supports Live mode (real Azure Service Bus) and Mock mode (in-process for local development). Queues, topics, and subscriptions are created automatically on first use.

๐Ÿ‘‰ Full documentation โ†’

๐Ÿ‡ RabbitMQ Integration

For self-hosted or on-premise scenarios, ChannelMediator.RabbitMQ provides the same distributed messaging patterns using RabbitMQ:

  • mediator.NotifyRabbitMq(notification) โ†’ Publishes to a Fanout Exchange (fan-out to all bound queues)
  • mediator.EnqueueRabbitMqRequest(request) โ†’ Enqueues to a Queue (competing consumers, only one processes each message)
var mediator = app.Services.GetRequiredService<IMediator>();

// Fan-out notification to all subscriber services
await mediator.Notify(new ProductAddedNotification("SKU-001", 5, 49.95m));

// Enqueue a request for competing consumer processing
await mediator.EnqueueRequest(new MyRequest("process-order-42"));

Supports Live mode (real RabbitMQ broker) and Mock mode (in-process for local development). Exchanges, queues, and bindings are created automatically on first use.

๐Ÿ‘‰ Full documentation โ†’

๐ŸŽฏ Use Cases

Perfect for:

  • โœ… High-load applications (backpressure)
  • โœ… Microservices with CQRS patterns
  • โœ… Migration from MediatR (drop-in replacement)
  • โœ… REST / gRPC APIs with complex orchestration
  • โœ… Event-driven architectures

Examples:

  • E-commerce: Orders, cart, checkout
  • CMS: Publishing, workflow, notifications
  • IoT: Telemetry, commands, events
  • Finance: Transactions, audit, reporting

โš™๏ธ Advanced Configuration

Parallel Notifications

services.AddChannelMediator(config => 
    config.Strategy = NotificationPublishStrategy.Parallel);

// All handlers execute in parallel with Task.WhenAll
await mediator.Publish(notification);

Sequential Notifications

services.AddChannelMediator(config => 
    config.Strategy = NotificationPublishStrategy.Sequential);

// Handlers execute one after another
await mediator.Publish(notification);

๐Ÿงช Tests

[Fact]
public async Task Should_Handle_Request()
{
    // Arrange
    var services = new ServiceCollection();
    services.AddChannelMediator(Assembly.GetExecutingAssembly());
    var provider = services.BuildServiceProvider();
    var mediator = provider.GetRequiredService<IMediator>();

    // Act
    var result = await mediator.Send(new AddToCartRequest("TEST"));

    // Assert
    Assert.NotNull(result);
    Assert.Equal("TEST", result.ProductCode);
}

๐Ÿ”ง Compatibility

  • .NET 10 (can be back-ported to .NET 8)
  • C# 14 (can be adapted for C# 12)
  • Microsoft.Extensions.DependencyInjection 9.0+

๐Ÿ“ License

MIT (to be defined)

๐Ÿ‘ฅ Contributing

Contributions are welcome! Open an issue or a PR.

๐Ÿ™ Inspirations

โญ Why ChannelMediator?

  1. Performance - Channel-based asynchronous processing
  2. Flexibility - MediatR-compatible API with powerful extensions
  3. Modern - .NET 10, C# 14, modern patterns
  4. Powerful - Global behaviors, parallel notifications
  5. Familiar - MediatR compatible, easy migration
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.0 106 5/18/2026
1.2.6 100 5/13/2026
1.2.5 108 5/11/2026
1.2.4 118 4/19/2026
1.1.3 98 4/19/2026
1.1.2 104 4/19/2026
1.1.1 102 4/18/2026
1.0.0 101 4/18/2026